]> git.sur5r.net Git - i3/i3/blob - src/commands.c
0263802fe4551c77c858f0397679e4aa112de4ca
[i3/i3] / src / commands.c
1 #undef I3__FILE__
2 #define I3__FILE__ "commands.c"
3 /*
4  * vim:ts=4:sw=4:expandtab
5  *
6  * i3 - an improved dynamic tiling window manager
7  * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
8  *
9  * commands.c: all command functions (see commands_parser.c)
10  *
11  */
12 #include <float.h>
13 #include <stdarg.h>
14
15 #include "all.h"
16
17 // Macros to make the YAJL API a bit easier to use.
18 #define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__)
19 #define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str))
20 #define ysuccess(success) do { \
21     y(map_open); \
22     ystr("success"); \
23     y(bool, success); \
24     y(map_close); \
25 } while (0)
26
27 /** When the command did not include match criteria (!), we use the currently
28  * focused container. Do not confuse this case with a command which included
29  * criteria but which did not match any windows. This macro has to be called in
30  * every command.
31  */
32 #define HANDLE_EMPTY_MATCH do { \
33     if (match_is_empty(current_match)) { \
34         owindow *ow = smalloc(sizeof(owindow)); \
35         ow->con = focused; \
36         TAILQ_INIT(&owindows); \
37         TAILQ_INSERT_TAIL(&owindows, ow, owindows); \
38     } \
39 } while (0)
40
41 static owindows_head owindows;
42
43 /*
44  * Returns true if a is definitely greater than b (using the given epsilon)
45  *
46  */
47 static bool definitelyGreaterThan(float a, float b, float epsilon) {
48     return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
49 }
50
51 /*
52  * Returns an 'output' corresponding to one of left/right/down/up or a specific
53  * output name.
54  *
55  */
56 static Output *get_output_from_string(Output *current_output, const char *output_str) {
57     Output *output;
58
59     if (strcasecmp(output_str, "left") == 0) {
60         output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
61         if (!output)
62             output = get_output_most(D_RIGHT, current_output);
63     } else if (strcasecmp(output_str, "right") == 0) {
64         output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
65         if (!output)
66             output = get_output_most(D_LEFT, current_output);
67     } else if (strcasecmp(output_str, "up") == 0) {
68         output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
69         if (!output)
70             output = get_output_most(D_DOWN, current_output);
71     } else if (strcasecmp(output_str, "down") == 0) {
72         output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
73         if (!output)
74             output = get_output_most(D_UP, current_output);
75     } else output = get_output_by_name(output_str);
76
77     return output;
78 }
79
80 /*
81  * Checks whether we switched to a new workspace and returns false in that case,
82  * signaling that further workspace switching should be done by the calling function
83  * If not, calls workspace_back_and_forth() if workspace_auto_back_and_forth is set
84  * and return true, signaling that no further workspace switching should occur in the calling function.
85  *
86  */
87 static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) {
88     Con *ws = con_get_workspace(focused);
89
90     /* If we switched to a different workspace, do nothing */
91     if (strcmp(ws->name, name) != 0)
92         return false;
93
94     DLOG("This workspace is already focused.\n");
95     if (config.workspace_auto_back_and_forth) {
96         workspace_back_and_forth();
97         cmd_output->needs_tree_render = true;
98     }
99     return true;
100 }
101
102 /*
103  * Return the passed workspace unless it is the current one and auto back and
104  * forth is enabled, in which case the back_and_forth workspace is returned.
105  */
106 static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
107     Con *current, *baf;
108
109     if (!config.workspace_auto_back_and_forth)
110         return workspace;
111
112     current = con_get_workspace(focused);
113
114     if (current == workspace) {
115         baf = workspace_back_and_forth_get();
116         if (baf != NULL) {
117             DLOG("Substituting workspace with back_and_forth, as it is focused.\n");
118             return baf;
119         }
120     }
121
122     return workspace;
123 }
124
125 // This code is commented out because we might recycle it for popping up error
126 // messages on parser errors.
127 #if 0
128 static pid_t migration_pid = -1;
129
130 /*
131  * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
132  * it exited (or could not be started, depending on the exit code).
133  *
134  */
135 static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
136     ev_child_stop(EV_A_ watcher);
137     if (!WIFEXITED(watcher->rstatus)) {
138         fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
139         return;
140     }
141
142     int exitcode = WEXITSTATUS(watcher->rstatus);
143     printf("i3-nagbar process exited with status %d\n", exitcode);
144     if (exitcode == 2) {
145         fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
146     }
147
148     migration_pid = -1;
149 }
150
151 /* We need ev >= 4 for the following code. Since it is not *that* important (it
152  * only makes sure that there are no i3-nagbar instances left behind) we still
153  * support old systems with libev 3. */
154 #if EV_VERSION_MAJOR >= 4
155 /*
156  * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
157  * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
158  *
159  */
160 static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
161     if (migration_pid != -1) {
162         LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", migration_pid);
163         kill(migration_pid, SIGKILL);
164     }
165 }
166 #endif
167
168 void cmd_MIGRATION_start_nagbar(void) {
169     if (migration_pid != -1) {
170         fprintf(stderr, "i3-nagbar already running.\n");
171         return;
172     }
173     fprintf(stderr, "Starting i3-nagbar, command parsing differs from expected output.\n");
174     ELOG("Please report this on IRC or in the bugtracker. Make sure to include the full debug level logfile:\n");
175     ELOG("i3-dump-log | gzip -9c > /tmp/i3.log.gz\n");
176     ELOG("FYI: Your i3 version is " I3_VERSION "\n");
177     migration_pid = fork();
178     if (migration_pid == -1) {
179         warn("Could not fork()");
180         return;
181     }
182
183     /* child */
184     if (migration_pid == 0) {
185         char *pageraction;
186         sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename);
187         char *argv[] = {
188             NULL, /* will be replaced by the executable path */
189             "-t",
190             "error",
191             "-m",
192             "You found a parsing error. Please, please, please, report it!",
193             "-b",
194             "show errors",
195             pageraction,
196             NULL
197         };
198         exec_i3_utility("i3-nagbar", argv);
199     }
200
201     /* parent */
202     /* install a child watcher */
203     ev_child *child = smalloc(sizeof(ev_child));
204     ev_child_init(child, &nagbar_exited, migration_pid, 0);
205     ev_child_start(main_loop, child);
206
207 /* We need ev >= 4 for the following code. Since it is not *that* important (it
208  * only makes sure that there are no i3-nagbar instances left behind) we still
209  * support old systems with libev 3. */
210 #if EV_VERSION_MAJOR >= 4
211     /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
212      * still running) */
213     ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
214     ev_cleanup_init(cleanup, nagbar_cleanup);
215     ev_cleanup_start(main_loop, cleanup);
216 #endif
217 }
218
219 #endif
220
221 /*******************************************************************************
222  * Criteria functions.
223  ******************************************************************************/
224
225 /*
226  * Initializes the specified 'Match' data structure and the initial state of
227  * commands.c for matching target windows of a command.
228  *
229  */
230 void cmd_criteria_init(I3_CMD) {
231     Con *con;
232     owindow *ow;
233
234     DLOG("Initializing criteria, current_match = %p\n", current_match);
235     match_init(current_match);
236     while (!TAILQ_EMPTY(&owindows)) {
237         ow = TAILQ_FIRST(&owindows);
238         TAILQ_REMOVE(&owindows, ow, owindows);
239         free(ow);
240     }
241     TAILQ_INIT(&owindows);
242     /* copy all_cons */
243     TAILQ_FOREACH(con, &all_cons, all_cons) {
244         ow = smalloc(sizeof(owindow));
245         ow->con = con;
246         TAILQ_INSERT_TAIL(&owindows, ow, owindows);
247     }
248 }
249
250 /*
251  * A match specification just finished (the closing square bracket was found),
252  * so we filter the list of owindows.
253  *
254  */
255 void cmd_criteria_match_windows(I3_CMD) {
256     owindow *next, *current;
257
258     DLOG("match specification finished, matching...\n");
259     /* copy the old list head to iterate through it and start with a fresh
260      * list which will contain only matching windows */
261     struct owindows_head old = owindows;
262     TAILQ_INIT(&owindows);
263     for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) {
264         /* make a copy of the next pointer and advance the pointer to the
265          * next element as we are going to invalidate the element’s
266          * next/prev pointers by calling TAILQ_INSERT_TAIL later */
267         current = next;
268         next = TAILQ_NEXT(next, owindows);
269
270         DLOG("checking if con %p / %s matches\n", current->con, current->con->name);
271         if (current_match->con_id != NULL) {
272             if (current_match->con_id == current->con) {
273                 DLOG("matches container!\n");
274                 TAILQ_INSERT_TAIL(&owindows, current, owindows);
275             }
276         } else if (current_match->mark != NULL && current->con->mark != NULL &&
277                    regex_matches(current_match->mark, current->con->mark)) {
278             DLOG("match by mark\n");
279             TAILQ_INSERT_TAIL(&owindows, current, owindows);
280         } else {
281             if (current->con->window == NULL)
282                 continue;
283             if (match_matches_window(current_match, current->con->window)) {
284                 DLOG("matches window!\n");
285                 TAILQ_INSERT_TAIL(&owindows, current, owindows);
286             } else {
287                 DLOG("doesnt match\n");
288                 free(current);
289             }
290         }
291     }
292
293     TAILQ_FOREACH(current, &owindows, owindows) {
294         DLOG("matching: %p / %s\n", current->con, current->con->name);
295     }
296 }
297
298 /*
299  * Interprets a ctype=cvalue pair and adds it to the current match
300  * specification.
301  *
302  */
303 void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) {
304     DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue);
305
306     if (strcmp(ctype, "class") == 0) {
307         current_match->class = regex_new(cvalue);
308         return;
309     }
310
311     if (strcmp(ctype, "instance") == 0) {
312         current_match->instance = regex_new(cvalue);
313         return;
314     }
315
316     if (strcmp(ctype, "window_role") == 0) {
317         current_match->role = regex_new(cvalue);
318         return;
319     }
320
321     if (strcmp(ctype, "con_id") == 0) {
322         char *end;
323         long parsed = strtol(cvalue, &end, 10);
324         if (parsed == LONG_MIN ||
325             parsed == LONG_MAX ||
326             parsed < 0 ||
327             (end && *end != '\0')) {
328             ELOG("Could not parse con id \"%s\"\n", cvalue);
329         } else {
330             current_match->con_id = (Con*)parsed;
331             printf("id as int = %p\n", current_match->con_id);
332         }
333         return;
334     }
335
336     if (strcmp(ctype, "id") == 0) {
337         char *end;
338         long parsed = strtol(cvalue, &end, 10);
339         if (parsed == LONG_MIN ||
340             parsed == LONG_MAX ||
341             parsed < 0 ||
342             (end && *end != '\0')) {
343             ELOG("Could not parse window id \"%s\"\n", cvalue);
344         } else {
345             current_match->id = parsed;
346             printf("window id as int = %d\n", current_match->id);
347         }
348         return;
349     }
350
351     if (strcmp(ctype, "con_mark") == 0) {
352         current_match->mark = regex_new(cvalue);
353         return;
354     }
355
356     if (strcmp(ctype, "title") == 0) {
357         current_match->title = regex_new(cvalue);
358         return;
359     }
360
361     if (strcmp(ctype, "urgent") == 0) {
362         if (strcasecmp(cvalue, "latest") == 0 ||
363             strcasecmp(cvalue, "newest") == 0 ||
364             strcasecmp(cvalue, "recent") == 0 ||
365             strcasecmp(cvalue, "last") == 0) {
366             current_match->urgent = U_LATEST;
367         } else if (strcasecmp(cvalue, "oldest") == 0 ||
368                    strcasecmp(cvalue, "first") == 0) {
369             current_match->urgent = U_OLDEST;
370         }
371         return;
372     }
373
374     ELOG("Unknown criterion: %s\n", ctype);
375 }
376
377 /*
378  * Implementation of 'move [window|container] [to] workspace
379  * next|prev|next_on_output|prev_on_output|current'.
380  *
381  */
382 void cmd_move_con_to_workspace(I3_CMD, char *which) {
383     owindow *current;
384
385     DLOG("which=%s\n", which);
386
387     /* We have nothing to move:
388      *  when criteria was specified but didn't match any window or
389      *  when criteria wasn't specified and we don't have any window focused. */
390     if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
391         (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
392         ysuccess(false);
393         return;
394     }
395
396     HANDLE_EMPTY_MATCH;
397
398     /* get the workspace */
399     Con *ws;
400     if (strcmp(which, "next") == 0)
401         ws = workspace_next();
402     else if (strcmp(which, "prev") == 0)
403         ws = workspace_prev();
404     else if (strcmp(which, "next_on_output") == 0)
405         ws = workspace_next_on_output();
406     else if (strcmp(which, "prev_on_output") == 0)
407         ws = workspace_prev_on_output();
408     else if (strcmp(which, "current") == 0)
409         ws = con_get_workspace(focused);
410     else {
411         ELOG("BUG: called with which=%s\n", which);
412         ysuccess(false);
413         return;
414     }
415
416     TAILQ_FOREACH(current, &owindows, owindows) {
417         DLOG("matching: %p / %s\n", current->con, current->con->name);
418         con_move_to_workspace(current->con, ws, true, false);
419     }
420
421     cmd_output->needs_tree_render = true;
422     // XXX: default reply for now, make this a better reply
423     ysuccess(true);
424 }
425
426 /**
427  * Implementation of 'move [window|container] [to] workspace back_and_forth'.
428  *
429  */
430 void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
431     owindow *current;
432     Con *ws;
433
434     ws = workspace_back_and_forth_get();
435
436     if (ws == NULL) {
437         y(map_open);
438         ystr("success");
439         y(bool, false);
440         ystr("error");
441         ystr("No workspace was previously active.");
442         y(map_close);
443         return;
444     }
445
446     HANDLE_EMPTY_MATCH;
447
448     TAILQ_FOREACH(current, &owindows, owindows) {
449         DLOG("matching: %p / %s\n", current->con, current->con->name);
450         con_move_to_workspace(current->con, ws, true, false);
451     }
452
453     cmd_output->needs_tree_render = true;
454     // XXX: default reply for now, make this a better reply
455     ysuccess(true);
456 }
457
458 /*
459  * Implementation of 'move [window|container] [to] workspace <name>'.
460  *
461  */
462 void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
463     if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
464         LOG("You cannot switch to the i3 internal workspaces.\n");
465         ysuccess(false);
466         return;
467     }
468
469     owindow *current;
470
471     /* We have nothing to move:
472      *  when criteria was specified but didn't match any window or
473      *  when criteria wasn't specified and we don't have any window focused. */
474     if (!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) {
475         ELOG("No windows match your criteria, cannot move.\n");
476         ysuccess(false);
477         return;
478     }
479
480     if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
481         ELOG("No window to move, you have focused a workspace.\n");
482         ysuccess(false);
483         return;
484     }
485
486     LOG("should move window to workspace %s\n", name);
487     /* get the workspace */
488     Con *ws = workspace_get(name, NULL);
489
490     ws = maybe_auto_back_and_forth_workspace(ws);
491
492     HANDLE_EMPTY_MATCH;
493
494     TAILQ_FOREACH(current, &owindows, owindows) {
495         DLOG("matching: %p / %s\n", current->con, current->con->name);
496         con_move_to_workspace(current->con, ws, true, false);
497     }
498
499     cmd_output->needs_tree_render = true;
500     // XXX: default reply for now, make this a better reply
501     ysuccess(true);
502 }
503
504 /*
505  * Implementation of 'move [window|container] [to] workspace number <name>'.
506  *
507  */
508 void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
509     owindow *current;
510
511     /* We have nothing to move:
512      *  when criteria was specified but didn't match any window or
513      *  when criteria wasn't specified and we don't have any window focused. */
514     if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
515         (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
516         ysuccess(false);
517         return;
518     }
519
520     LOG("should move window to workspace %s\n", which);
521     /* get the workspace */
522     Con *output, *workspace = NULL;
523
524     char *endptr = NULL;
525     long parsed_num = strtol(which, &endptr, 10);
526     if (parsed_num == LONG_MIN ||
527         parsed_num == LONG_MAX ||
528         parsed_num < 0 ||
529         endptr == which) {
530         LOG("Could not parse initial part of \"%s\" as a number.\n", which);
531         y(map_open);
532         ystr("success");
533         y(bool, false);
534         ystr("error");
535         // TODO: better error message
536         ystr("Could not parse number");
537         y(map_close);
538         return;
539     }
540
541     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
542         GREP_FIRST(workspace, output_get_content(output),
543             child->num == parsed_num);
544
545     if (!workspace) {
546         workspace = workspace_get(which, NULL);
547     }
548
549     workspace = maybe_auto_back_and_forth_workspace(workspace);
550
551     HANDLE_EMPTY_MATCH;
552
553     TAILQ_FOREACH(current, &owindows, owindows) {
554         DLOG("matching: %p / %s\n", current->con, current->con->name);
555         con_move_to_workspace(current->con, workspace, true, false);
556     }
557
558     cmd_output->needs_tree_render = true;
559     // XXX: default reply for now, make this a better reply
560     ysuccess(true);
561 }
562
563 static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) {
564     LOG("floating resize\n");
565     if (strcmp(direction, "up") == 0) {
566         floating_con->rect.y -= px;
567         floating_con->rect.height += px;
568     } else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
569         floating_con->rect.height += px;
570     } else if (strcmp(direction, "left") == 0) {
571         floating_con->rect.x -= px;
572         floating_con->rect.width += px;
573     } else {
574         floating_con->rect.width += px;
575     }
576 }
577
578 static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) {
579     LOG("tiling resize\n");
580     /* get the appropriate current container (skip stacked/tabbed cons) */
581     Con *other = NULL;
582     double percentage = 0;
583     while (current->parent->layout == L_STACKED ||
584            current->parent->layout == L_TABBED)
585         current = current->parent;
586
587     /* Then further go up until we find one with the matching orientation. */
588     orientation_t search_orientation =
589         (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
590
591     do {
592         if (con_orientation(current->parent) != search_orientation) {
593             current = current->parent;
594             continue;
595         }
596
597         /* get the default percentage */
598         int children = con_num_children(current->parent);
599         LOG("ins. %d children\n", children);
600         percentage = 1.0 / children;
601         LOG("default percentage = %f\n", percentage);
602
603         orientation_t orientation = con_orientation(current->parent);
604
605         if ((orientation == HORIZ &&
606              (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
607             (orientation == VERT &&
608              (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
609             LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
610                 (orientation == HORIZ ? "horizontal" : "vertical"));
611             ysuccess(false);
612             return false;
613         }
614
615         if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
616             other = TAILQ_PREV(current, nodes_head, nodes);
617         } else {
618             other = TAILQ_NEXT(current, nodes);
619         }
620         if (other == TAILQ_END(workspaces)) {
621             LOG("No other container in this direction found, trying to look further up in the tree...\n");
622             current = current->parent;
623             continue;
624         }
625         break;
626     } while (current->type != CT_WORKSPACE &&
627              current->type != CT_FLOATING_CON);
628
629     if (other == NULL) {
630         LOG("No other container in this direction found, trying to look further up in the tree...\n");
631         ysuccess(false);
632         return false;
633     }
634
635     LOG("other->percent = %f\n", other->percent);
636     LOG("current->percent before = %f\n", current->percent);
637     if (current->percent == 0.0)
638         current->percent = percentage;
639     if (other->percent == 0.0)
640         other->percent = percentage;
641     double new_current_percent = current->percent + ((double)ppt / 100.0);
642     double new_other_percent = other->percent - ((double)ppt / 100.0);
643     LOG("new_current_percent = %f\n", new_current_percent);
644     LOG("new_other_percent = %f\n", new_other_percent);
645     /* Ensure that the new percentages are positive and greater than
646      * 0.05 to have a reasonable minimum size. */
647     if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) &&
648         definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) {
649         current->percent += ((double)ppt / 100.0);
650         other->percent -= ((double)ppt / 100.0);
651         LOG("current->percent after = %f\n", current->percent);
652         LOG("other->percent after = %f\n", other->percent);
653     } else {
654         LOG("Not resizing, already at minimum size\n");
655     }
656
657     return true;
658 }
659
660 static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char *direction, int ppt) {
661     LOG("width/height resize\n");
662     /* get the appropriate current container (skip stacked/tabbed cons) */
663     while (current->parent->layout == L_STACKED ||
664            current->parent->layout == L_TABBED)
665         current = current->parent;
666
667     /* Then further go up until we find one with the matching orientation. */
668     orientation_t search_orientation =
669         (strcmp(direction, "width") == 0 ? HORIZ : VERT);
670
671     while (current->type != CT_WORKSPACE &&
672            current->type != CT_FLOATING_CON &&
673            con_orientation(current->parent) != search_orientation)
674         current = current->parent;
675
676     /* get the default percentage */
677     int children = con_num_children(current->parent);
678     LOG("ins. %d children\n", children);
679     double percentage = 1.0 / children;
680     LOG("default percentage = %f\n", percentage);
681
682     orientation_t orientation = con_orientation(current->parent);
683
684     if ((orientation == HORIZ &&
685          strcmp(direction, "height") == 0) ||
686         (orientation == VERT &&
687          strcmp(direction, "width") == 0)) {
688         LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
689             (orientation == HORIZ ? "horizontal" : "vertical"));
690         ysuccess(false);
691         return false;
692     }
693
694     if (children == 1) {
695         LOG("This is the only container, cannot resize.\n");
696         ysuccess(false);
697         return false;
698     }
699
700     /* Ensure all the other children have a percentage set. */
701     Con *child;
702     TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
703         LOG("child->percent = %f (child %p)\n", child->percent, child);
704         if (child->percent == 0.0)
705             child->percent = percentage;
706     }
707
708     double new_current_percent = current->percent + ((double)ppt / 100.0);
709     double subtract_percent = ((double)ppt / 100.0) / (children - 1);
710     LOG("new_current_percent = %f\n", new_current_percent);
711     LOG("subtract_percent = %f\n", subtract_percent);
712     /* Ensure that the new percentages are positive and greater than
713      * 0.05 to have a reasonable minimum size. */
714     TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
715         if (child == current)
716             continue;
717         if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) {
718             LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
719             ysuccess(false);
720             return false;
721         }
722     }
723     if (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) {
724         LOG("Not resizing, already at minimum size\n");
725         ysuccess(false);
726         return false;
727     }
728
729     current->percent += ((double)ppt / 100.0);
730     LOG("current->percent after = %f\n", current->percent);
731
732     TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
733         if (child == current)
734             continue;
735         child->percent -= subtract_percent;
736         LOG("child->percent after (%p) = %f\n", child, child->percent);
737     }
738
739     return true;
740 }
741
742 /*
743  * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
744  *
745  */
746 void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt) {
747     /* resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt] */
748     DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt);
749     // 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
750     int px = atoi(resize_px);
751     int ppt = atoi(resize_ppt);
752     if (strcmp(way, "shrink") == 0) {
753         px *= -1;
754         ppt *= -1;
755     }
756
757     HANDLE_EMPTY_MATCH;
758
759     owindow *current;
760     TAILQ_FOREACH(current, &owindows, owindows) {
761         Con *floating_con;
762         if ((floating_con = con_inside_floating(current->con))) {
763             cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px);
764         } else {
765             if (strcmp(direction, "width") == 0 ||
766                 strcmp(direction, "height") == 0) {
767                 if (!cmd_resize_tiling_width_height(current_match, cmd_output, current->con, way, direction, ppt))
768                     return;
769             } else {
770                 if (!cmd_resize_tiling_direction(current_match, cmd_output, current->con, way, direction, ppt))
771                     return;
772             }
773         }
774     }
775
776     cmd_output->needs_tree_render = true;
777     // XXX: default reply for now, make this a better reply
778     ysuccess(true);
779 }
780
781 /*
782  * Implementation of 'border normal|none|1pixel|toggle|pixel'.
783  *
784  */
785 void cmd_border(I3_CMD, char *border_style_str, char *border_width ) {
786     DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width);
787     owindow *current;
788
789     HANDLE_EMPTY_MATCH;
790
791     TAILQ_FOREACH(current, &owindows, owindows) {
792         DLOG("matching: %p / %s\n", current->con, current->con->name);
793         int border_style = current->con->border_style;
794         char *end;
795         int tmp_border_width = -1;
796         tmp_border_width = strtol(border_width, &end, 10);
797         if (end == border_width) {
798             /* no valid digits found */
799             tmp_border_width = -1;
800         }
801         if (strcmp(border_style_str, "toggle") == 0) {
802             border_style++;
803             border_style %= 3;
804             if (border_style == BS_NORMAL)
805                 tmp_border_width = 2;
806             else if (border_style == BS_NONE)
807                 tmp_border_width = 0;
808             else if (border_style == BS_PIXEL)
809                 tmp_border_width = 1;
810         } else {
811             if (strcmp(border_style_str, "normal") == 0)
812                 border_style = BS_NORMAL;
813             else if (strcmp(border_style_str, "pixel") == 0)
814                 border_style = BS_PIXEL;
815             else if (strcmp(border_style_str, "1pixel") == 0){
816                 border_style = BS_PIXEL;
817                 tmp_border_width = 1;
818             } else if (strcmp(border_style_str, "none") == 0)
819                 border_style = BS_NONE;
820             else {
821                 ELOG("BUG: called with border_style=%s\n", border_style_str);
822                 ysuccess(false);
823                 return;
824             }
825         }
826         con_set_border_style(current->con, border_style, tmp_border_width);
827     }
828
829     cmd_output->needs_tree_render = true;
830     // XXX: default reply for now, make this a better reply
831     ysuccess(true);
832 }
833
834 /*
835  * Implementation of 'nop <comment>'.
836  *
837  */
838 void cmd_nop(I3_CMD, char *comment) {
839     LOG("-------------------------------------------------\n");
840     LOG("  NOP: %s\n", comment);
841     LOG("-------------------------------------------------\n");
842 }
843
844 /*
845  * Implementation of 'append_layout <path>'.
846  *
847  */
848 void cmd_append_layout(I3_CMD, char *path) {
849     LOG("Appending layout \"%s\"\n", path);
850     tree_append_json(path);
851
852     cmd_output->needs_tree_render = true;
853     // XXX: default reply for now, make this a better reply
854     ysuccess(true);
855 }
856
857 /*
858  * Implementation of 'workspace next|prev|next_on_output|prev_on_output'.
859  *
860  */
861 void cmd_workspace(I3_CMD, char *which) {
862     Con *ws;
863
864     DLOG("which=%s\n", which);
865
866     if (strcmp(which, "next") == 0)
867         ws = workspace_next();
868     else if (strcmp(which, "prev") == 0)
869         ws = workspace_prev();
870     else if (strcmp(which, "next_on_output") == 0)
871         ws = workspace_next_on_output();
872     else if (strcmp(which, "prev_on_output") == 0)
873         ws = workspace_prev_on_output();
874     else {
875         ELOG("BUG: called with which=%s\n", which);
876         ysuccess(false);
877         return;
878     }
879
880     workspace_show(ws);
881
882     cmd_output->needs_tree_render = true;
883     // XXX: default reply for now, make this a better reply
884     ysuccess(true);
885 }
886
887 /*
888  * Implementation of 'workspace number <name>'
889  *
890  */
891 void cmd_workspace_number(I3_CMD, char *which) {
892     Con *output, *workspace = NULL;
893
894     char *endptr = NULL;
895     long parsed_num = strtol(which, &endptr, 10);
896     if (parsed_num == LONG_MIN ||
897         parsed_num == LONG_MAX ||
898         parsed_num < 0 ||
899         endptr == which) {
900         LOG("Could not parse initial part of \"%s\" as a number.\n", which);
901         y(map_open);
902         ystr("success");
903         y(bool, false);
904         ystr("error");
905         // TODO: better error message
906         ystr("Could not parse number");
907         y(map_close);
908
909         return;
910     }
911
912     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
913         GREP_FIRST(workspace, output_get_content(output),
914             child->num == parsed_num);
915
916     if (!workspace) {
917         LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
918         ysuccess(true);
919         workspace_show_by_name(which);
920         cmd_output->needs_tree_render = true;
921         return;
922     }
923     if (maybe_back_and_forth(cmd_output, workspace->name))
924         return;
925     workspace_show(workspace);
926
927     cmd_output->needs_tree_render = true;
928     // XXX: default reply for now, make this a better reply
929     ysuccess(true);
930 }
931
932 /*
933  * Implementation of 'workspace back_and_forth'.
934  *
935  */
936 void cmd_workspace_back_and_forth(I3_CMD) {
937     workspace_back_and_forth();
938
939     cmd_output->needs_tree_render = true;
940     // XXX: default reply for now, make this a better reply
941     ysuccess(true);
942 }
943
944 /*
945  * Implementation of 'workspace <name>'
946  *
947  */
948 void cmd_workspace_name(I3_CMD, char *name) {
949     if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
950         LOG("You cannot switch to the i3 internal workspaces.\n");
951         ysuccess(false);
952         return;
953     }
954
955     DLOG("should switch to workspace %s\n", name);
956     if (maybe_back_and_forth(cmd_output, name))
957        return;
958     workspace_show_by_name(name);
959
960     cmd_output->needs_tree_render = true;
961     // XXX: default reply for now, make this a better reply
962     ysuccess(true);
963 }
964
965 /*
966  * Implementation of 'mark <mark>'
967  *
968  */
969 void cmd_mark(I3_CMD, char *mark) {
970     DLOG("Clearing all windows which have that mark first\n");
971
972     Con *con;
973     TAILQ_FOREACH(con, &all_cons, all_cons) {
974         if (con->mark && strcmp(con->mark, mark) == 0)
975             FREE(con->mark);
976     }
977
978     DLOG("marking window with str %s\n", mark);
979     owindow *current;
980
981     HANDLE_EMPTY_MATCH;
982
983     TAILQ_FOREACH(current, &owindows, owindows) {
984         DLOG("matching: %p / %s\n", current->con, current->con->name);
985         current->con->mark = sstrdup(mark);
986     }
987
988     cmd_output->needs_tree_render = true;
989     // XXX: default reply for now, make this a better reply
990     ysuccess(true);
991 }
992
993 /*
994  * Implementation of 'mode <string>'.
995  *
996  */
997 void cmd_mode(I3_CMD, char *mode) {
998     DLOG("mode=%s\n", mode);
999     switch_mode(mode);
1000
1001     // XXX: default reply for now, make this a better reply
1002     ysuccess(true);
1003 }
1004
1005 /*
1006  * Implementation of 'move [window|container] [to] output <str>'.
1007  *
1008  */
1009 void cmd_move_con_to_output(I3_CMD, char *name) {
1010     owindow *current;
1011
1012     DLOG("should move window to output %s\n", name);
1013
1014     HANDLE_EMPTY_MATCH;
1015
1016     /* get the output */
1017     Output *current_output = NULL;
1018     Output *output;
1019
1020     // TODO: fix the handling of criteria
1021     TAILQ_FOREACH(current, &owindows, owindows)
1022         current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
1023
1024     assert(current_output != NULL);
1025
1026     // TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
1027     if (strcasecmp(name, "up") == 0)
1028         output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
1029     else if (strcasecmp(name, "down") == 0)
1030         output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
1031     else if (strcasecmp(name, "left") == 0)
1032         output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
1033     else if (strcasecmp(name, "right") == 0)
1034         output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
1035     else
1036         output = get_output_by_name(name);
1037
1038     if (!output) {
1039         LOG("No such output found.\n");
1040         ysuccess(false);
1041         return;
1042     }
1043
1044     /* get visible workspace on output */
1045     Con *ws = NULL;
1046     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
1047     if (!ws) {
1048         ysuccess(false);
1049         return;
1050     }
1051
1052     TAILQ_FOREACH(current, &owindows, owindows) {
1053         DLOG("matching: %p / %s\n", current->con, current->con->name);
1054         con_move_to_workspace(current->con, ws, true, false);
1055     }
1056
1057     cmd_output->needs_tree_render = true;
1058     // XXX: default reply for now, make this a better reply
1059     ysuccess(true);
1060 }
1061
1062 /*
1063  * Implementation of 'floating enable|disable|toggle'
1064  *
1065  */
1066 void cmd_floating(I3_CMD, char *floating_mode) {
1067     owindow *current;
1068
1069     DLOG("floating_mode=%s\n", floating_mode);
1070
1071     HANDLE_EMPTY_MATCH;
1072
1073     TAILQ_FOREACH(current, &owindows, owindows) {
1074         DLOG("matching: %p / %s\n", current->con, current->con->name);
1075         if (strcmp(floating_mode, "toggle") == 0) {
1076             DLOG("should toggle mode\n");
1077             toggle_floating_mode(current->con, false);
1078         } else {
1079             DLOG("should switch mode to %s\n", floating_mode);
1080             if (strcmp(floating_mode, "enable") == 0) {
1081                 floating_enable(current->con, false);
1082             } else {
1083                 floating_disable(current->con, false);
1084             }
1085         }
1086     }
1087
1088     cmd_output->needs_tree_render = true;
1089     // XXX: default reply for now, make this a better reply
1090     ysuccess(true);
1091 }
1092
1093 /*
1094  * Implementation of 'move workspace to [output] <str>'.
1095  *
1096  */
1097 void cmd_move_workspace_to_output(I3_CMD, char *name) {
1098     DLOG("should move workspace to output %s\n", name);
1099
1100     HANDLE_EMPTY_MATCH;
1101
1102     owindow *current;
1103     TAILQ_FOREACH(current, &owindows, owindows) {
1104         Output *current_output = get_output_containing(current->con->rect.x,
1105                                                        current->con->rect.y);
1106         if (!current_output) {
1107             ELOG("Cannot get current output. This is a bug in i3.\n");
1108             ysuccess(false);
1109             return;
1110         }
1111         Output *output = get_output_from_string(current_output, name);
1112         if (!output) {
1113             ELOG("Could not get output from string \"%s\"\n", name);
1114             ysuccess(false);
1115             return;
1116         }
1117
1118         Con *content = output_get_content(output->con);
1119         LOG("got output %p with content %p\n", output, content);
1120
1121         Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head));
1122         LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
1123
1124         Con *ws = con_get_workspace(current->con);
1125         LOG("should move workspace %p / %s\n", ws, ws->name);
1126         bool workspace_was_visible = workspace_is_visible(ws);
1127
1128         if (con_num_children(ws->parent) == 1) {
1129             LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
1130
1131             /* check if we can find a workspace assigned to this output */
1132             bool used_assignment = false;
1133             struct Workspace_Assignment *assignment;
1134             TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
1135                 if (strcmp(assignment->output, current_output->name) != 0)
1136                     continue;
1137
1138                 /* check if this workspace is already attached to the tree */
1139                 Con *workspace = NULL, *out;
1140                 TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
1141                     GREP_FIRST(workspace, output_get_content(out),
1142                                !strcasecmp(child->name, assignment->name));
1143                 if (workspace != NULL)
1144                     continue;
1145
1146                 /* so create the workspace referenced to by this assignment */
1147                 LOG("Creating workspace from assignment %s.\n", assignment->name);
1148                 workspace_get(assignment->name, NULL);
1149                 used_assignment = true;
1150                 break;
1151             }
1152
1153             /* if we couldn't create the workspace using an assignment, create
1154              * it on the output */
1155             if (!used_assignment)
1156                 create_workspace_on_output(current_output, ws->parent);
1157
1158             /* notify the IPC listeners */
1159             ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
1160         }
1161         DLOG("Detaching\n");
1162
1163         /* detach from the old output and attach to the new output */
1164         Con *old_content = ws->parent;
1165         con_detach(ws);
1166         if (workspace_was_visible) {
1167             /* The workspace which we just detached was visible, so focus
1168              * the next one in the focus-stack. */
1169             Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head));
1170             LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name);
1171             workspace_show(focus_ws);
1172         }
1173         con_attach(ws, content, false);
1174
1175         /* fix the coordinates of the floating containers */
1176         Con *floating_con;
1177         TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows)
1178             floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect));
1179
1180         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"move\"}");
1181         if (workspace_was_visible) {
1182             /* Focus the moved workspace on the destination output. */
1183             workspace_show(ws);
1184         }
1185
1186         /* NB: We cannot simply work with previously_visible_ws since it might
1187          * have been cleaned up by workspace_show() already, depending on the
1188          * focus order/number of other workspaces on the output.
1189          * Instead, we loop through the available workspaces and only work with
1190          * previously_visible_ws if we still find it. */
1191         TAILQ_FOREACH(ws, &(content->nodes_head), nodes) {
1192             if (ws != previously_visible_ws)
1193                 continue;
1194
1195             /* Call the on_remove_child callback of the workspace which previously
1196              * was visible on the destination output. Since it is no longer
1197              * visible, it might need to get cleaned up. */
1198             CALL(previously_visible_ws, on_remove_child);
1199             break;
1200         }
1201     }
1202
1203     cmd_output->needs_tree_render = true;
1204     // XXX: default reply for now, make this a better reply
1205     ysuccess(true);
1206 }
1207
1208 /*
1209  * Implementation of 'split v|h|vertical|horizontal'.
1210  *
1211  */
1212 void cmd_split(I3_CMD, char *direction) {
1213     owindow *current;
1214     /* TODO: use matches */
1215     LOG("splitting in direction %c\n", direction[0]);
1216     if (match_is_empty(current_match))
1217         tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ));
1218     else {
1219         TAILQ_FOREACH(current, &owindows, owindows) {
1220             DLOG("matching: %p / %s\n", current->con, current->con->name);
1221             tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ));
1222         }
1223     }
1224
1225     cmd_output->needs_tree_render = true;
1226     // XXX: default reply for now, make this a better reply
1227     ysuccess(true);
1228 }
1229
1230 /*
1231  * Implementaiton of 'kill [window|client]'.
1232  *
1233  */
1234 void cmd_kill(I3_CMD, char *kill_mode_str) {
1235     if (kill_mode_str == NULL)
1236         kill_mode_str = "window";
1237     owindow *current;
1238
1239     DLOG("kill_mode=%s\n", kill_mode_str);
1240
1241     int kill_mode;
1242     if (strcmp(kill_mode_str, "window") == 0)
1243         kill_mode = KILL_WINDOW;
1244     else if (strcmp(kill_mode_str, "client") == 0)
1245         kill_mode = KILL_CLIENT;
1246     else {
1247         ELOG("BUG: called with kill_mode=%s\n", kill_mode_str);
1248         ysuccess(false);
1249         return;
1250     }
1251
1252     /* check if the match is empty, not if the result is empty */
1253     if (match_is_empty(current_match))
1254         tree_close_con(kill_mode);
1255     else {
1256         TAILQ_FOREACH(current, &owindows, owindows) {
1257             DLOG("matching: %p / %s\n", current->con, current->con->name);
1258             tree_close(current->con, kill_mode, false, false);
1259         }
1260     }
1261
1262     cmd_output->needs_tree_render = true;
1263     // XXX: default reply for now, make this a better reply
1264     ysuccess(true);
1265 }
1266
1267 /*
1268  * Implementation of 'exec [--no-startup-id] <command>'.
1269  *
1270  */
1271 void cmd_exec(I3_CMD, char *nosn, char *command) {
1272     bool no_startup_id = (nosn != NULL);
1273
1274     DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
1275     start_application(command, no_startup_id);
1276
1277     // XXX: default reply for now, make this a better reply
1278     ysuccess(true);
1279 }
1280
1281 /*
1282  * Implementation of 'focus left|right|up|down'.
1283  *
1284  */
1285 void cmd_focus_direction(I3_CMD, char *direction) {
1286     if (focused &&
1287         focused->type != CT_WORKSPACE &&
1288         focused->fullscreen_mode != CF_NONE) {
1289         LOG("Cannot change focus while in fullscreen mode.\n");
1290         ysuccess(false);
1291         return;
1292     }
1293
1294     DLOG("direction = *%s*\n", direction);
1295
1296     if (strcmp(direction, "left") == 0)
1297         tree_next('p', HORIZ);
1298     else if (strcmp(direction, "right") == 0)
1299         tree_next('n', HORIZ);
1300     else if (strcmp(direction, "up") == 0)
1301         tree_next('p', VERT);
1302     else if (strcmp(direction, "down") == 0)
1303         tree_next('n', VERT);
1304     else {
1305         ELOG("Invalid focus direction (%s)\n", direction);
1306         ysuccess(false);
1307         return;
1308     }
1309
1310     cmd_output->needs_tree_render = true;
1311     // XXX: default reply for now, make this a better reply
1312     ysuccess(true);
1313 }
1314
1315 /*
1316  * Implementation of 'focus tiling|floating|mode_toggle'.
1317  *
1318  */
1319 void cmd_focus_window_mode(I3_CMD, char *window_mode) {
1320     if (focused &&
1321         focused->type != CT_WORKSPACE &&
1322         focused->fullscreen_mode != CF_NONE) {
1323         LOG("Cannot change focus while in fullscreen mode.\n");
1324         ysuccess(false);
1325         return;
1326     }
1327
1328     DLOG("window_mode = %s\n", window_mode);
1329
1330     Con *ws = con_get_workspace(focused);
1331     Con *current;
1332     if (ws != NULL) {
1333         if (strcmp(window_mode, "mode_toggle") == 0) {
1334             current = TAILQ_FIRST(&(ws->focus_head));
1335             if (current != NULL && current->type == CT_FLOATING_CON)
1336                 window_mode = "tiling";
1337             else window_mode = "floating";
1338         }
1339         TAILQ_FOREACH(current, &(ws->focus_head), focused) {
1340             if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) ||
1341                 (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON))
1342                 continue;
1343
1344             con_focus(con_descend_focused(current));
1345             break;
1346         }
1347     }
1348
1349     cmd_output->needs_tree_render = true;
1350     // XXX: default reply for now, make this a better reply
1351     ysuccess(true);
1352 }
1353
1354 /*
1355  * Implementation of 'focus parent|child'.
1356  *
1357  */
1358 void cmd_focus_level(I3_CMD, char *level) {
1359     DLOG("level = %s\n", level);
1360     bool success = false;
1361
1362     /* Focusing the parent can only be allowed if the newly
1363      * focused container won't escape the fullscreen container. */
1364     if (strcmp(level, "parent") == 0) {
1365         if (focused && focused->parent) {
1366             if (con_fullscreen_permits_focusing(focused->parent))
1367                 success = level_up();
1368             else
1369                 ELOG("'focus parent': Currently in fullscreen, not going up\n");
1370         }
1371     }
1372
1373     /* Focusing a child should always be allowed. */
1374     else success = level_down();
1375
1376     cmd_output->needs_tree_render = success;
1377     // XXX: default reply for now, make this a better reply
1378     ysuccess(success);
1379 }
1380
1381 /*
1382  * Implementation of 'focus'.
1383  *
1384  */
1385 void cmd_focus(I3_CMD) {
1386     DLOG("current_match = %p\n", current_match);
1387
1388     if (match_is_empty(current_match)) {
1389         ELOG("You have to specify which window/container should be focused.\n");
1390         ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
1391
1392         y(map_open);
1393         ystr("success");
1394         y(bool, false);
1395         ystr("error");
1396         ystr("You have to specify which window/container should be focused");
1397         y(map_close);
1398
1399         return;
1400     }
1401
1402     int count = 0;
1403     owindow *current;
1404     TAILQ_FOREACH(current, &owindows, owindows) {
1405         Con *ws = con_get_workspace(current->con);
1406         /* If no workspace could be found, this was a dock window.
1407          * Just skip it, you cannot focus dock windows. */
1408         if (!ws)
1409             continue;
1410
1411         /* Check the fullscreen focus constraints. */
1412         if (!con_fullscreen_permits_focusing(current->con)) {
1413             LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n");
1414             ysuccess(false);
1415             return;
1416         }
1417
1418         /* If the container is not on the current workspace,
1419          * workspace_show() will switch to a different workspace and (if
1420          * enabled) trigger a mouse pointer warp to the currently focused
1421          * container (!) on the target workspace.
1422          *
1423          * Therefore, before calling workspace_show(), we make sure that
1424          * 'current' will be focused on the workspace. However, we cannot
1425          * just con_focus(current) because then the pointer will not be
1426          * warped at all (the code thinks we are already there).
1427          *
1428          * So we focus 'current' to make it the currently focused window of
1429          * the target workspace, then revert focus. */
1430         Con *currently_focused = focused;
1431         con_focus(current->con);
1432         con_focus(currently_focused);
1433
1434         /* Now switch to the workspace, then focus */
1435         workspace_show(ws);
1436         LOG("focusing %p / %s\n", current->con, current->con->name);
1437         con_focus(current->con);
1438         count++;
1439     }
1440
1441     if (count > 1)
1442         LOG("WARNING: Your criteria for the focus command matches %d containers, "
1443             "while only exactly one container can be focused at a time.\n", count);
1444
1445     cmd_output->needs_tree_render = true;
1446     // XXX: default reply for now, make this a better reply
1447     ysuccess(true);
1448 }
1449
1450 /*
1451  * Implementation of 'fullscreen [global]'.
1452  *
1453  */
1454 void cmd_fullscreen(I3_CMD, char *fullscreen_mode) {
1455     if (fullscreen_mode == NULL)
1456         fullscreen_mode = "output";
1457     DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode);
1458     owindow *current;
1459
1460     HANDLE_EMPTY_MATCH;
1461
1462     TAILQ_FOREACH(current, &owindows, owindows) {
1463         printf("matching: %p / %s\n", current->con, current->con->name);
1464         con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT));
1465     }
1466
1467     cmd_output->needs_tree_render = true;
1468     // XXX: default reply for now, make this a better reply
1469     ysuccess(true);
1470 }
1471
1472 /*
1473  * Implementation of 'move <direction> [<pixels> [px]]'.
1474  *
1475  */
1476 void cmd_move_direction(I3_CMD, char *direction, char *move_px) {
1477     // 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
1478     int px = atoi(move_px);
1479
1480     /* TODO: make 'move' work with criteria. */
1481     DLOG("moving in direction %s, px %s\n", direction, move_px);
1482     if (con_is_floating(focused)) {
1483         DLOG("floating move with %d pixels\n", px);
1484         Rect newrect = focused->parent->rect;
1485         if (strcmp(direction, "left") == 0) {
1486             newrect.x -= px;
1487         } else if (strcmp(direction, "right") == 0) {
1488             newrect.x += px;
1489         } else if (strcmp(direction, "up") == 0) {
1490             newrect.y -= px;
1491         } else if (strcmp(direction, "down") == 0) {
1492             newrect.y += px;
1493         }
1494         floating_reposition(focused->parent, newrect);
1495     } else {
1496         tree_move((strcmp(direction, "right") == 0 ? D_RIGHT :
1497                    (strcmp(direction, "left") == 0 ? D_LEFT :
1498                     (strcmp(direction, "up") == 0 ? D_UP :
1499                      D_DOWN))));
1500         cmd_output->needs_tree_render = true;
1501     }
1502
1503     // XXX: default reply for now, make this a better reply
1504     ysuccess(true);
1505 }
1506
1507 /*
1508  * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
1509  *
1510  */
1511 void cmd_layout(I3_CMD, char *layout_str) {
1512     if (strcmp(layout_str, "stacking") == 0)
1513         layout_str = "stacked";
1514     owindow *current;
1515     int layout;
1516     /* default is a special case which will be handled in con_set_layout(). */
1517     if (strcmp(layout_str, "default") == 0)
1518         layout = L_DEFAULT;
1519     else if (strcmp(layout_str, "stacked") == 0)
1520         layout = L_STACKED;
1521     else if (strcmp(layout_str, "tabbed") == 0)
1522         layout = L_TABBED;
1523     else if (strcmp(layout_str, "splitv") == 0)
1524         layout = L_SPLITV;
1525     else if (strcmp(layout_str, "splith") == 0)
1526         layout = L_SPLITH;
1527     else {
1528         ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
1529         return;
1530     }
1531
1532     DLOG("changing layout to %s (%d)\n", layout_str, layout);
1533
1534     /* check if the match is empty, not if the result is empty */
1535     if (match_is_empty(current_match))
1536         con_set_layout(focused, layout);
1537     else {
1538         TAILQ_FOREACH(current, &owindows, owindows) {
1539             DLOG("matching: %p / %s\n", current->con, current->con->name);
1540             con_set_layout(current->con, layout);
1541         }
1542     }
1543
1544     cmd_output->needs_tree_render = true;
1545     // XXX: default reply for now, make this a better reply
1546     ysuccess(true);
1547 }
1548
1549 /*
1550  * Implementation of 'layout toggle [all|split]'.
1551  *
1552  */
1553 void cmd_layout_toggle(I3_CMD, char *toggle_mode) {
1554     owindow *current;
1555
1556     if (toggle_mode == NULL)
1557         toggle_mode = "default";
1558
1559     DLOG("toggling layout (mode = %s)\n", toggle_mode);
1560
1561     /* check if the match is empty, not if the result is empty */
1562     if (match_is_empty(current_match))
1563         con_toggle_layout(focused, toggle_mode);
1564     else {
1565         TAILQ_FOREACH(current, &owindows, owindows) {
1566             DLOG("matching: %p / %s\n", current->con, current->con->name);
1567             con_toggle_layout(current->con, toggle_mode);
1568         }
1569     }
1570
1571     cmd_output->needs_tree_render = true;
1572     // XXX: default reply for now, make this a better reply
1573     ysuccess(true);
1574 }
1575
1576 /*
1577  * Implementaiton of 'exit'.
1578  *
1579  */
1580 void cmd_exit(I3_CMD) {
1581     LOG("Exiting due to user command.\n");
1582     xcb_disconnect(conn);
1583     exit(0);
1584
1585     /* unreached */
1586 }
1587
1588 /*
1589  * Implementaiton of 'reload'.
1590  *
1591  */
1592 void cmd_reload(I3_CMD) {
1593     LOG("reloading\n");
1594     kill_configerror_nagbar(false);
1595     kill_commanderror_nagbar(false);
1596     load_configuration(conn, NULL, true);
1597     x_set_i3_atoms();
1598     /* Send an IPC event just in case the ws names have changed */
1599     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
1600
1601     // XXX: default reply for now, make this a better reply
1602     ysuccess(true);
1603 }
1604
1605 /*
1606  * Implementaiton of 'restart'.
1607  *
1608  */
1609 void cmd_restart(I3_CMD) {
1610     LOG("restarting i3\n");
1611     i3_restart(false);
1612
1613     // XXX: default reply for now, make this a better reply
1614     ysuccess(true);
1615 }
1616
1617 /*
1618  * Implementaiton of 'open'.
1619  *
1620  */
1621 void cmd_open(I3_CMD) {
1622     LOG("opening new container\n");
1623     Con *con = tree_open_con(NULL, NULL);
1624     con->layout = L_SPLITH;
1625     con_focus(con);
1626
1627     y(map_open);
1628     ystr("success");
1629     y(bool, true);
1630     ystr("id");
1631     y(integer, (long int)con);
1632     y(map_close);
1633
1634     cmd_output->needs_tree_render = true;
1635 }
1636
1637 /*
1638  * Implementation of 'focus output <output>'.
1639  *
1640  */
1641 void cmd_focus_output(I3_CMD, char *name) {
1642     owindow *current;
1643
1644     DLOG("name = %s\n", name);
1645
1646     HANDLE_EMPTY_MATCH;
1647
1648     /* get the output */
1649     Output *current_output = NULL;
1650     Output *output;
1651
1652     TAILQ_FOREACH(current, &owindows, owindows)
1653         current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
1654     assert(current_output != NULL);
1655
1656     output = get_output_from_string(current_output, name);
1657
1658     if (!output) {
1659         LOG("No such output found.\n");
1660         ysuccess(false);
1661         return;
1662     }
1663
1664     /* get visible workspace on output */
1665     Con *ws = NULL;
1666     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
1667     if (!ws) {
1668         ysuccess(false);
1669         return;
1670     }
1671
1672     workspace_show(ws);
1673
1674     cmd_output->needs_tree_render = true;
1675     // XXX: default reply for now, make this a better reply
1676     ysuccess(true);
1677 }
1678
1679 /*
1680  * Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px]
1681  *
1682  */
1683 void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) {
1684
1685     int x = atoi(cx);
1686     int y = atoi(cy);
1687
1688     if (!con_is_floating(focused)) {
1689         ELOG("Cannot change position. The window/container is not floating\n");
1690         y(map_open);
1691         ystr("success");
1692         y(bool, false);
1693         ystr("error");
1694         ystr("Cannot change position. The window/container is not floating.");
1695         y(map_close);
1696         return;
1697     }
1698
1699     if (strcmp(method, "absolute") == 0) {
1700         focused->parent->rect.x = x;
1701         focused->parent->rect.y = y;
1702
1703         DLOG("moving to absolute position %d %d\n", x, y);
1704         floating_maybe_reassign_ws(focused->parent);
1705         cmd_output->needs_tree_render = true;
1706     }
1707
1708     if (strcmp(method, "position") == 0) {
1709         Rect newrect = focused->parent->rect;
1710
1711         DLOG("moving to position %d %d\n", x, y);
1712         newrect.x = x;
1713         newrect.y = y;
1714
1715         floating_reposition(focused->parent, newrect);
1716     }
1717
1718     // XXX: default reply for now, make this a better reply
1719     ysuccess(true);
1720 }
1721
1722 /*
1723  * Implementation of 'move [window|container] [to] [absolute] position center
1724  *
1725  */
1726 void cmd_move_window_to_center(I3_CMD, char *method) {
1727
1728     if (!con_is_floating(focused)) {
1729         ELOG("Cannot change position. The window/container is not floating\n");
1730         y(map_open);
1731         ystr("success");
1732         y(bool, false);
1733         ystr("error");
1734         ystr("Cannot change position. The window/container is not floating.");
1735         y(map_close);
1736     }
1737
1738     if (strcmp(method, "absolute") == 0) {
1739         Rect *rect = &focused->parent->rect;
1740
1741         DLOG("moving to absolute center\n");
1742         rect->x = croot->rect.width/2 - rect->width/2;
1743         rect->y = croot->rect.height/2 - rect->height/2;
1744
1745         floating_maybe_reassign_ws(focused->parent);
1746         cmd_output->needs_tree_render = true;
1747     }
1748
1749     if (strcmp(method, "position") == 0) {
1750         Rect *wsrect = &con_get_workspace(focused)->rect;
1751         Rect newrect = focused->parent->rect;
1752
1753         DLOG("moving to center\n");
1754         newrect.x = wsrect->width/2 - newrect.width/2;
1755         newrect.y = wsrect->height/2 - newrect.height/2;
1756
1757         floating_reposition(focused->parent, newrect);
1758     }
1759
1760     // XXX: default reply for now, make this a better reply
1761     ysuccess(true);
1762 }
1763
1764 /*
1765  * Implementation of 'move scratchpad'.
1766  *
1767  */
1768 void cmd_move_scratchpad(I3_CMD) {
1769     DLOG("should move window to scratchpad\n");
1770     owindow *current;
1771
1772     HANDLE_EMPTY_MATCH;
1773
1774     TAILQ_FOREACH(current, &owindows, owindows) {
1775         DLOG("matching: %p / %s\n", current->con, current->con->name);
1776         scratchpad_move(current->con);
1777     }
1778
1779     cmd_output->needs_tree_render = true;
1780     // XXX: default reply for now, make this a better reply
1781     ysuccess(true);
1782 }
1783
1784 /*
1785  * Implementation of 'scratchpad show'.
1786  *
1787  */
1788 void cmd_scratchpad_show(I3_CMD) {
1789     DLOG("should show scratchpad window\n");
1790     owindow *current;
1791
1792     if (match_is_empty(current_match)) {
1793         scratchpad_show(NULL);
1794     } else {
1795         TAILQ_FOREACH(current, &owindows, owindows) {
1796             DLOG("matching: %p / %s\n", current->con, current->con->name);
1797             scratchpad_show(current->con);
1798         }
1799     }
1800
1801     cmd_output->needs_tree_render = true;
1802     // XXX: default reply for now, make this a better reply
1803     ysuccess(true);
1804 }
1805
1806 /*
1807  * Implementation of 'rename workspace <name> to <name>'
1808  *
1809  */
1810 void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
1811     LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name);
1812
1813     Con *output, *workspace = NULL;
1814     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
1815         GREP_FIRST(workspace, output_get_content(output),
1816             !strcasecmp(child->name, old_name));
1817
1818     if (!workspace) {
1819         // TODO: we should include the old workspace name here and use yajl for
1820         // generating the reply.
1821         y(map_open);
1822         ystr("success");
1823         y(bool, false);
1824         ystr("error");
1825         // TODO: better error message
1826         ystr("Old workspace not found");
1827         y(map_close);
1828         return;
1829     }
1830
1831     Con *check_dest = NULL;
1832     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
1833         GREP_FIRST(check_dest, output_get_content(output),
1834             !strcasecmp(child->name, new_name));
1835
1836     if (check_dest != NULL) {
1837         // TODO: we should include the new workspace name here and use yajl for
1838         // generating the reply.
1839         y(map_open);
1840         ystr("success");
1841         y(bool, false);
1842         ystr("error");
1843         // TODO: better error message
1844         ystr("New workspace already exists");
1845         y(map_close);
1846         return;
1847     }
1848
1849     /* Change the name and try to parse it as a number. */
1850     FREE(workspace->name);
1851     workspace->name = sstrdup(new_name);
1852     char *endptr = NULL;
1853     long parsed_num = strtol(new_name, &endptr, 10);
1854     if (parsed_num == LONG_MIN ||
1855         parsed_num == LONG_MAX ||
1856         parsed_num < 0 ||
1857         endptr == new_name)
1858         workspace->num = -1;
1859     else workspace->num = parsed_num;
1860     LOG("num = %d\n", workspace->num);
1861
1862     /* By re-attaching, the sort order will be correct afterwards. */
1863     Con *previously_focused = focused;
1864     Con *parent = workspace->parent;
1865     con_detach(workspace);
1866     con_attach(workspace, parent, false);
1867     /* Restore the previous focus since con_attach messes with the focus. */
1868     con_focus(previously_focused);
1869
1870     cmd_output->needs_tree_render = true;
1871     ysuccess(true);
1872
1873     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}");
1874 }