]> git.sur5r.net Git - i3/i3/blob - src/commands.c
7c4b9a613d7ab204da7e5d9a91332fd3009a2afa
[i3/i3] / src / commands.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  * commands.c: all command functions (see commands_parser.c)
8  *
9  */
10 #include <float.h>
11
12 #include "all.h"
13 #include "cmdparse.tab.h"
14
15 /** When the command did not include match criteria (!), we use the currently
16  * focused command. Do not confuse this case with a command which included
17  * criteria but which did not match any windows. This macro has to be called in
18  * every command.
19  */
20 #define HANDLE_EMPTY_MATCH do { \
21     if (match_is_empty(current_match)) { \
22         owindow *ow = smalloc(sizeof(owindow)); \
23         ow->con = focused; \
24         TAILQ_INIT(&owindows); \
25         TAILQ_INSERT_TAIL(&owindows, ow, owindows); \
26     } \
27 } while (0)
28
29 static owindows_head owindows;
30
31 /*
32  * Returns true if a is definitely greater than b (using the given epsilon)
33  *
34  */
35 static bool definitelyGreaterThan(float a, float b, float epsilon) {
36     return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
37 }
38
39 static Output *get_output_from_string(Output *current_output, const char *output_str) {
40     Output *output;
41
42     if (strcasecmp(output_str, "left") == 0) {
43         output = get_output_next(D_LEFT, current_output);
44         if (!output)
45             output = get_output_most(D_RIGHT, current_output);
46     } else if (strcasecmp(output_str, "right") == 0) {
47         output = get_output_next(D_RIGHT, current_output);
48         if (!output)
49             output = get_output_most(D_LEFT, current_output);
50     } else if (strcasecmp(output_str, "up") == 0) {
51         output = get_output_next(D_UP, current_output);
52         if (!output)
53             output = get_output_most(D_DOWN, current_output);
54     } else if (strcasecmp(output_str, "down") == 0) {
55         output = get_output_next(D_DOWN, current_output);
56         if (!output)
57             output = get_output_most(D_UP, current_output);
58     } else output = get_output_by_name(output_str);
59
60     return output;
61 }
62
63 char *cmd_criteria_init(Match *current_match) {
64     DLOG("Initializing criteria, current_match = %p\n", current_match);
65     match_init(current_match);
66     TAILQ_INIT(&owindows);
67     /* copy all_cons */
68     Con *con;
69     TAILQ_FOREACH(con, &all_cons, all_cons) {
70         owindow *ow = smalloc(sizeof(owindow));
71         ow->con = con;
72         TAILQ_INSERT_TAIL(&owindows, ow, owindows);
73     }
74
75     /* This command is internal and does not generate a JSON reply. */
76     return NULL;
77 }
78
79 char *cmd_criteria_match_windows(Match *current_match) {
80     owindow *next, *current;
81
82     DLOG("match specification finished, matching...\n");
83     /* copy the old list head to iterate through it and start with a fresh
84      * list which will contain only matching windows */
85     struct owindows_head old = owindows;
86     TAILQ_INIT(&owindows);
87     for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) {
88         /* make a copy of the next pointer and advance the pointer to the
89          * next element as we are going to invalidate the element’s
90          * next/prev pointers by calling TAILQ_INSERT_TAIL later */
91         current = next;
92         next = TAILQ_NEXT(next, owindows);
93
94         DLOG("checking if con %p / %s matches\n", current->con, current->con->name);
95         if (current_match->con_id != NULL) {
96             if (current_match->con_id == current->con) {
97                 DLOG("matches container!\n");
98                 TAILQ_INSERT_TAIL(&owindows, current, owindows);
99             }
100         } else if (current_match->mark != NULL && current->con->mark != NULL &&
101                    regex_matches(current_match->mark, current->con->mark)) {
102             DLOG("match by mark\n");
103             TAILQ_INSERT_TAIL(&owindows, current, owindows);
104         } else {
105             if (current->con->window == NULL)
106                 continue;
107             if (match_matches_window(current_match, current->con->window)) {
108                 DLOG("matches window!\n");
109                 TAILQ_INSERT_TAIL(&owindows, current, owindows);
110             } else {
111                 DLOG("doesnt match\n");
112                 free(current);
113             }
114         }
115     }
116
117     TAILQ_FOREACH(current, &owindows, owindows) {
118         DLOG("matching: %p / %s\n", current->con, current->con->name);
119     }
120
121     /* This command is internal and does not generate a JSON reply. */
122     return NULL;
123 }
124
125 char *cmd_criteria_add(Match *current_match, char *ctype, char *cvalue) {
126     DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue);
127
128     if (strcmp(ctype, "class") == 0) {
129         current_match->class = regex_new(cvalue);
130         return NULL;
131     }
132
133     if (strcmp(ctype, "instance") == 0) {
134         current_match->instance = regex_new(cvalue);
135         return NULL;
136     }
137
138     if (strcmp(ctype, "window_role") == 0) {
139         current_match->role = regex_new(cvalue);
140         return NULL;
141     }
142
143     if (strcmp(ctype, "con_id") == 0) {
144         char *end;
145         long parsed = strtol(cvalue, &end, 10);
146         if (parsed == LONG_MIN ||
147             parsed == LONG_MAX ||
148             parsed < 0 ||
149             (end && *end != '\0')) {
150             ELOG("Could not parse con id \"%s\"\n", cvalue);
151         } else {
152             current_match->con_id = (Con*)parsed;
153             printf("id as int = %p\n", current_match->con_id);
154         }
155         return NULL;
156     }
157
158     if (strcmp(ctype, "id") == 0) {
159         char *end;
160         long parsed = strtol(cvalue, &end, 10);
161         if (parsed == LONG_MIN ||
162             parsed == LONG_MAX ||
163             parsed < 0 ||
164             (end && *end != '\0')) {
165             ELOG("Could not parse window id \"%s\"\n", cvalue);
166         } else {
167             current_match->id = parsed;
168             printf("window id as int = %d\n", current_match->id);
169         }
170         return NULL;
171     }
172
173     if (strcmp(ctype, "con_mark") == 0) {
174         current_match->mark = regex_new(cvalue);
175         return NULL;
176     }
177
178     if (strcmp(ctype, "title") == 0) {
179         current_match->title = regex_new(cvalue);
180         return NULL;
181     }
182
183     ELOG("Unknown criterion: %s\n", ctype);
184
185     /* This command is internal and does not generate a JSON reply. */
186     return NULL;
187 }
188
189 char *cmd_move_con_to_workspace(Match *current_match, char *which) {
190     owindow *current;
191
192     DLOG("which=%s\n", which);
193
194     HANDLE_EMPTY_MATCH;
195
196     /* get the workspace */
197     Con *ws;
198     if (strcmp(which, "next") == 0)
199         ws = workspace_next();
200     else if (strcmp(which, "prev") == 0)
201         ws = workspace_prev();
202     else if (strcmp(which, "next_on_output") == 0)
203         ws = workspace_next_on_output();
204     else if (strcmp(which, "prev_on_output") == 0)
205         ws = workspace_prev_on_output();
206     else {
207         ELOG("BUG: called with which=%s\n", which);
208         return sstrdup("{\"sucess\": false}");
209     }
210
211     TAILQ_FOREACH(current, &owindows, owindows) {
212         DLOG("matching: %p / %s\n", current->con, current->con->name);
213         con_move_to_workspace(current->con, ws, true, false);
214     }
215
216     tree_render();
217
218     // XXX: default reply for now, make this a better reply
219     return sstrdup("{\"success\": true}");
220 }
221
222 char *cmd_move_con_to_workspace_name(Match *current_match, char *name) {
223     if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
224         LOG("You cannot switch to the i3 internal workspaces.\n");
225         return sstrdup("{\"sucess\": false}");
226     }
227
228     owindow *current;
229
230     /* Error out early to not create a non-existing workspace (in
231      * workspace_get()) if we are not actually able to move anything. */
232     if (match_is_empty(current_match) && focused->type == CT_WORKSPACE)
233         return sstrdup("{\"sucess\": false}");
234
235     LOG("should move window to workspace %s\n", name);
236     /* get the workspace */
237     Con *ws = workspace_get(name, NULL);
238
239     HANDLE_EMPTY_MATCH;
240
241     TAILQ_FOREACH(current, &owindows, owindows) {
242         DLOG("matching: %p / %s\n", current->con, current->con->name);
243         con_move_to_workspace(current->con, ws, true, false);
244     }
245
246     tree_render();
247
248     // XXX: default reply for now, make this a better reply
249     return sstrdup("{\"success\": true}");
250 }
251
252 char *cmd_resize(Match *current_match, char *way, char *direction, char *resize_px, char *resize_ppt) {
253     /* resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt] */
254     DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt);
255     // 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
256     int px = atoi(resize_px);
257     int ppt = atoi(resize_ppt);
258     if (strcmp(way, "shrink") == 0) {
259         px *= -1;
260         ppt *= -1;
261     }
262
263     Con *floating_con;
264     if ((floating_con = con_inside_floating(focused))) {
265         printf("floating resize\n");
266         if (strcmp(direction, "up") == 0) {
267             floating_con->rect.y -= px;
268             floating_con->rect.height += px;
269         } else if (strcmp(direction, "down") == 0) {
270             floating_con->rect.height += px;
271         } else if (strcmp(direction, "left") == 0) {
272             floating_con->rect.x -= px;
273             floating_con->rect.width += px;
274         } else {
275             floating_con->rect.width += px;
276         }
277     } else {
278         LOG("tiling resize\n");
279         /* get the appropriate current container (skip stacked/tabbed cons) */
280         Con *current = focused;
281         while (current->parent->layout == L_STACKED ||
282                current->parent->layout == L_TABBED)
283             current = current->parent;
284
285         /* Then further go up until we find one with the matching orientation. */
286         orientation_t search_orientation =
287             (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
288
289         while (current->type != CT_WORKSPACE &&
290                current->type != CT_FLOATING_CON &&
291                current->parent->orientation != search_orientation)
292             current = current->parent;
293
294         /* get the default percentage */
295         int children = con_num_children(current->parent);
296         Con *other;
297         LOG("ins. %d children\n", children);
298         double percentage = 1.0 / children;
299         LOG("default percentage = %f\n", percentage);
300
301         orientation_t orientation = current->parent->orientation;
302
303         if ((orientation == HORIZ &&
304              (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
305             (orientation == VERT &&
306              (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
307             LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
308                 (orientation == HORIZ ? "horizontal" : "vertical"));
309             return sstrdup("{\"sucess\": false}");
310         }
311
312         if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
313             other = TAILQ_PREV(current, nodes_head, nodes);
314         } else {
315             other = TAILQ_NEXT(current, nodes);
316         }
317         if (other == TAILQ_END(workspaces)) {
318             LOG("No other container in this direction found, cannot resize.\n");
319             return sstrdup("{\"sucess\": false}");
320         }
321         LOG("other->percent = %f\n", other->percent);
322         LOG("current->percent before = %f\n", current->percent);
323         if (current->percent == 0.0)
324             current->percent = percentage;
325         if (other->percent == 0.0)
326             other->percent = percentage;
327         double new_current_percent = current->percent + ((double)ppt / 100.0);
328         double new_other_percent = other->percent - ((double)ppt / 100.0);
329         LOG("new_current_percent = %f\n", new_current_percent);
330         LOG("new_other_percent = %f\n", new_other_percent);
331         /* Ensure that the new percentages are positive and greater than
332          * 0.05 to have a reasonable minimum size. */
333         if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) &&
334             definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) {
335             current->percent += ((double)ppt / 100.0);
336             other->percent -= ((double)ppt / 100.0);
337             LOG("current->percent after = %f\n", current->percent);
338             LOG("other->percent after = %f\n", other->percent);
339         } else {
340             LOG("Not resizing, already at minimum size\n");
341         }
342     }
343
344     tree_render();
345
346     // XXX: default reply for now, make this a better reply
347     return sstrdup("{\"success\": true}");
348 }
349
350 char *cmd_border(Match *current_match, char *border_style_str) {
351     DLOG("border style should be changed to %s\n", border_style_str);
352     owindow *current;
353
354     HANDLE_EMPTY_MATCH;
355
356     TAILQ_FOREACH(current, &owindows, owindows) {
357         DLOG("matching: %p / %s\n", current->con, current->con->name);
358         int border_style = current->con->border_style;
359         if (strcmp(border_style_str, "toggle") == 0) {
360             border_style++;
361             border_style %= 3;
362         } else {
363             if (strcmp(border_style_str, "normal") == 0)
364                 border_style = BS_NORMAL;
365             else if (strcmp(border_style_str, "none") == 0)
366                 border_style = BS_NONE;
367             else if (strcmp(border_style_str, "1pixel") == 0)
368                 border_style = BS_1PIXEL;
369             else {
370                 ELOG("BUG: called with border_style=%s\n", border_style_str);
371                 return sstrdup("{\"sucess\": false}");
372             }
373         }
374         con_set_border_style(current->con, border_style);
375     }
376
377     tree_render();
378
379     // XXX: default reply for now, make this a better reply
380     return sstrdup("{\"success\": true}");
381 }
382
383 char *cmd_nop(Match *current_match, char *comment) {
384     LOG("-------------------------------------------------\n");
385     LOG("  NOP: %s\n", comment);
386     LOG("-------------------------------------------------\n");
387
388     return NULL;
389 }
390
391 char *cmd_append_layout(Match *current_match, char *path) {
392     LOG("Appending layout \"%s\"\n", path);
393     tree_append_json(path);
394     tree_render();
395
396     // XXX: default reply for now, make this a better reply
397     return sstrdup("{\"success\": true}");
398 }
399
400 char *cmd_workspace(Match *current_match, char *which) {
401     Con *ws;
402
403     DLOG("which=%s\n", which);
404
405     if (strcmp(which, "next") == 0)
406         ws = workspace_next();
407     else if (strcmp(which, "prev") == 0)
408         ws = workspace_prev();
409     else if (strcmp(which, "next_on_output") == 0)
410         ws = workspace_next_on_output();
411     else if (strcmp(which, "prev_on_output") == 0)
412         ws = workspace_prev_on_output();
413     else {
414         ELOG("BUG: called with which=%s\n", which);
415         return sstrdup("{\"sucess\": false}");
416     }
417
418     workspace_show(ws);
419     tree_render();
420
421     // XXX: default reply for now, make this a better reply
422     return sstrdup("{\"success\": true}");
423 }
424
425 char *cmd_workspace_back_and_forth(Match *current_match) {
426     workspace_back_and_forth();
427     tree_render();
428
429     // XXX: default reply for now, make this a better reply
430     return sstrdup("{\"success\": true}");
431 }
432
433 char *cmd_workspace_name(Match *current_match, char *name) {
434     if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
435         LOG("You cannot switch to the i3 internal workspaces.\n");
436         return sstrdup("{\"sucess\": false}");
437     }
438
439     DLOG("should switch to workspace %s\n", name);
440
441     Con *ws = con_get_workspace(focused);
442
443     /* Check if the command wants to switch to the current workspace */
444     if (strcmp(ws->name, name) == 0) {
445         DLOG("This workspace is already focused.\n");
446         if (config.workspace_auto_back_and_forth) {
447             workspace_back_and_forth();
448             tree_render();
449         }
450         return sstrdup("{\"sucess\": false}");
451     }
452
453     workspace_show_by_name(name);
454
455     tree_render();
456
457     // XXX: default reply for now, make this a better reply
458     return sstrdup("{\"success\": true}");
459 }
460
461 char *cmd_mark(Match *current_match, char *mark) {
462     DLOG("Clearing all windows which have that mark first\n");
463
464     Con *con;
465     TAILQ_FOREACH(con, &all_cons, all_cons) {
466         if (con->mark && strcmp(con->mark, mark) == 0)
467             FREE(con->mark);
468     }
469
470     DLOG("marking window with str %s\n", mark);
471     owindow *current;
472
473     HANDLE_EMPTY_MATCH;
474
475     TAILQ_FOREACH(current, &owindows, owindows) {
476         DLOG("matching: %p / %s\n", current->con, current->con->name);
477         current->con->mark = sstrdup(mark);
478     }
479
480     tree_render();
481
482     // XXX: default reply for now, make this a better reply
483     return sstrdup("{\"success\": true}");
484 }
485
486 char *cmd_mode(Match *current_match, char *mode) {
487     DLOG("mode=%s\n", mode);
488     switch_mode(mode);
489
490     // XXX: default reply for now, make this a better reply
491     return sstrdup("{\"success\": true}");
492 }
493
494 char *cmd_move_con_to_output(Match *current_match, char *name) {
495     owindow *current;
496
497     DLOG("should move window to output %s\n", name);
498
499     HANDLE_EMPTY_MATCH;
500
501     /* get the output */
502     Output *current_output = NULL;
503     Output *output;
504
505     // TODO: fix the handling of criteria
506     TAILQ_FOREACH(current, &owindows, owindows)
507         current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
508
509     assert(current_output != NULL);
510
511     // TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
512     if (strcasecmp(name, "up") == 0)
513         output = get_output_next(D_UP, current_output);
514     else if (strcasecmp(name, "down") == 0)
515         output = get_output_next(D_DOWN, current_output);
516     else if (strcasecmp(name, "left") == 0)
517         output = get_output_next(D_LEFT, current_output);
518     else if (strcasecmp(name, "right") == 0)
519         output = get_output_next(D_RIGHT, current_output);
520     else
521         output = get_output_by_name(name);
522
523     if (!output) {
524         LOG("No such output found.\n");
525         return sstrdup("{\"sucess\": false}");
526     }
527
528     /* get visible workspace on output */
529     Con *ws = NULL;
530     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
531     if (!ws)
532         return sstrdup("{\"sucess\": false}");
533
534     TAILQ_FOREACH(current, &owindows, owindows) {
535         DLOG("matching: %p / %s\n", current->con, current->con->name);
536         con_move_to_workspace(current->con, ws, true, false);
537     }
538
539     tree_render();
540
541     // XXX: default reply for now, make this a better reply
542     return sstrdup("{\"success\": true}");
543 }
544
545 char *cmd_floating(Match *current_match, char *floating_mode) {
546     owindow *current;
547
548     DLOG("floating_mode=%s\n", floating_mode);
549
550     HANDLE_EMPTY_MATCH;
551
552     TAILQ_FOREACH(current, &owindows, owindows) {
553         DLOG("matching: %p / %s\n", current->con, current->con->name);
554         if (strcmp(floating_mode, "toggle") == 0) {
555             DLOG("should toggle mode\n");
556             toggle_floating_mode(current->con, false);
557         } else {
558             DLOG("should switch mode to %s\n", floating_mode);
559             if (strcmp(floating_mode, "enable") == 0) {
560                 floating_enable(current->con, false);
561             } else {
562                 floating_disable(current->con, false);
563             }
564         }
565     }
566
567     tree_render();
568
569     // XXX: default reply for now, make this a better reply
570     return sstrdup("{\"success\": true}");
571 }
572
573 char *cmd_move_workspace_to_output(Match *current_match, char *name) {
574     DLOG("should move workspace to output %s\n", name);
575
576     HANDLE_EMPTY_MATCH;
577
578     owindow *current;
579     TAILQ_FOREACH(current, &owindows, owindows) {
580         Output *current_output = get_output_containing(current->con->rect.x,
581                                                        current->con->rect.y);
582         Output *output = get_output_from_string(current_output, name);
583         if (!output) {
584             LOG("No such output\n");
585             return sstrdup("{\"sucess\": false}");
586         }
587
588         Con *content = output_get_content(output->con);
589         LOG("got output %p with content %p\n", output, content);
590
591         Con *ws = con_get_workspace(current->con);
592         LOG("should move workspace %p / %s\n", ws, ws->name);
593         if (con_num_children(ws->parent) == 1) {
594             LOG("Not moving workspace \"%s\", it is the only workspace on its output.\n", ws->name);
595             continue;
596         }
597         bool workspace_was_visible = workspace_is_visible(ws);
598         Con *old_content = ws->parent;
599         con_detach(ws);
600         if (workspace_was_visible) {
601             /* The workspace which we just detached was visible, so focus
602              * the next one in the focus-stack. */
603             Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head));
604             LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name);
605             workspace_show(focus_ws);
606         }
607         con_attach(ws, content, false);
608         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"move\"}");
609         if (workspace_was_visible) {
610             /* Focus the moved workspace on the destination output. */
611             workspace_show(ws);
612         }
613     }
614
615     tree_render();
616
617     // XXX: default reply for now, make this a better reply
618     return sstrdup("{\"success\": true}");
619 }
620
621 char *cmd_split(Match *current_match, char *direction) {
622     /* TODO: use matches */
623     LOG("splitting in direction %c\n", direction[0]);
624     tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ));
625
626     tree_render();
627
628     // XXX: default reply for now, make this a better reply
629     return sstrdup("{\"success\": true}");
630 }
631
632 char *cmd_kill(Match *current_match, char *kill_mode_str) {
633     owindow *current;
634
635     DLOG("kill_mode=%s\n", kill_mode_str);
636
637     int kill_mode;
638     if (kill_mode_str == NULL || strcmp(kill_mode_str, "window") == 0)
639         kill_mode = KILL_WINDOW;
640     else if (strcmp(kill_mode_str, "client") == 0)
641         kill_mode = KILL_CLIENT;
642     else {
643         ELOG("BUG: called with kill_mode=%s\n", kill_mode_str);
644         return sstrdup("{\"sucess\": false}");
645     }
646
647     /* check if the match is empty, not if the result is empty */
648     if (match_is_empty(current_match))
649         tree_close_con(kill_mode);
650     else {
651         TAILQ_FOREACH(current, &owindows, owindows) {
652             DLOG("matching: %p / %s\n", current->con, current->con->name);
653             tree_close(current->con, kill_mode, false, false);
654         }
655     }
656
657     tree_render();
658
659     // XXX: default reply for now, make this a better reply
660     return sstrdup("{\"success\": true}");
661 }
662
663 char *cmd_exec(Match *current_match, char *nosn, char *command) {
664     bool no_startup_id = (nosn != NULL);
665
666     DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
667     start_application(command, no_startup_id);
668
669     // XXX: default reply for now, make this a better reply
670     return sstrdup("{\"success\": true}");
671 }
672
673 char *cmd_focus_direction(Match *current_match, char *direction) {
674     if (focused &&
675         focused->type != CT_WORKSPACE &&
676         focused->fullscreen_mode != CF_NONE) {
677         LOG("Cannot change focus while in fullscreen mode.\n");
678         return sstrdup("{\"sucess\": false}");
679     }
680
681     DLOG("direction = *%s*\n", direction);
682
683     if (strcmp(direction, "left") == 0)
684         tree_next('p', HORIZ);
685     else if (strcmp(direction, "right") == 0)
686         tree_next('n', HORIZ);
687     else if (strcmp(direction, "up") == 0)
688         tree_next('p', VERT);
689     else if (strcmp(direction, "down") == 0)
690         tree_next('n', VERT);
691     else {
692         ELOG("Invalid focus direction (%s)\n", direction);
693         return sstrdup("{\"sucess\": false}");
694     }
695
696     tree_render();
697
698     // XXX: default reply for now, make this a better reply
699     return sstrdup("{\"success\": true}");
700 }
701
702 char *cmd_focus_window_mode(Match *current_match, char *window_mode) {
703     if (focused &&
704         focused->type != CT_WORKSPACE &&
705         focused->fullscreen_mode != CF_NONE) {
706         LOG("Cannot change focus while in fullscreen mode.\n");
707         return sstrdup("{\"sucess\": false}");
708     }
709
710     DLOG("window_mode = %s\n", window_mode);
711
712     Con *ws = con_get_workspace(focused);
713     Con *current;
714     if (ws != NULL) {
715         if (strcmp(window_mode, "mode_toggle") == 0) {
716             current = TAILQ_FIRST(&(ws->focus_head));
717             if (current != NULL && current->type == CT_FLOATING_CON)
718                 window_mode = "tiling";
719             else window_mode = "floating";
720         }
721         TAILQ_FOREACH(current, &(ws->focus_head), focused) {
722             if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) ||
723                 (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON))
724                 continue;
725
726             con_focus(con_descend_focused(current));
727             break;
728         }
729     }
730
731     tree_render();
732
733     // XXX: default reply for now, make this a better reply
734     return sstrdup("{\"success\": true}");
735 }
736
737 char *cmd_focus_level(Match *current_match, char *level) {
738     if (focused &&
739         focused->type != CT_WORKSPACE &&
740         focused->fullscreen_mode != CF_NONE) {
741         LOG("Cannot change focus while in fullscreen mode.\n");
742         return sstrdup("{\"sucess\": false}");
743     }
744
745     DLOG("level = %s\n", level);
746
747     if (strcmp(level, "parent") == 0)
748         level_up();
749     else level_down();
750
751     tree_render();
752
753     // XXX: default reply for now, make this a better reply
754     return sstrdup("{\"success\": true}");
755 }
756
757 char *cmd_focus(Match *current_match) {
758     DLOG("current_match = %p\n", current_match);
759     if (focused &&
760         focused->type != CT_WORKSPACE &&
761         focused->fullscreen_mode != CF_NONE) {
762         LOG("Cannot change focus while in fullscreen mode.\n");
763         return sstrdup("{\"sucess\": false}");
764     }
765
766     owindow *current;
767
768     if (match_is_empty(current_match)) {
769         ELOG("You have to specify which window/container should be focused.\n");
770         ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
771
772         // TODO: json output
773         char *json_output;
774         sasprintf(&json_output, "{\"success\":false, \"error\":\"You have to "
775                   "specify which window/container should be focused\"}");
776         return json_output;
777     }
778
779     LOG("here");
780     int count = 0;
781     TAILQ_FOREACH(current, &owindows, owindows) {
782         Con *ws = con_get_workspace(current->con);
783         /* If no workspace could be found, this was a dock window.
784          * Just skip it, you cannot focus dock windows. */
785         if (!ws)
786             continue;
787     LOG("there");
788
789         /* If the container is not on the current workspace,
790          * workspace_show() will switch to a different workspace and (if
791          * enabled) trigger a mouse pointer warp to the currently focused
792          * container (!) on the target workspace.
793          *
794          * Therefore, before calling workspace_show(), we make sure that
795          * 'current' will be focused on the workspace. However, we cannot
796          * just con_focus(current) because then the pointer will not be
797          * warped at all (the code thinks we are already there).
798          *
799          * So we focus 'current' to make it the currently focused window of
800          * the target workspace, then revert focus. */
801         Con *currently_focused = focused;
802         con_focus(current->con);
803         con_focus(currently_focused);
804
805         /* Now switch to the workspace, then focus */
806         workspace_show(ws);
807         LOG("focusing %p / %s\n", current->con, current->con->name);
808         con_focus(current->con);
809         count++;
810     }
811
812     if (count > 1)
813         LOG("WARNING: Your criteria for the focus command matches %d containers, "
814             "while only exactly one container can be focused at a time.\n", count);
815
816     tree_render();
817
818     // XXX: default reply for now, make this a better reply
819     return sstrdup("{\"success\": true}");
820 }
821
822 char *cmd_fullscreen(Match *current_match, char *fullscreen_mode) {
823     DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode);
824     owindow *current;
825
826     HANDLE_EMPTY_MATCH;
827
828     TAILQ_FOREACH(current, &owindows, owindows) {
829         printf("matching: %p / %s\n", current->con, current->con->name);
830         con_toggle_fullscreen(current->con, (fullscreen_mode && strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT));
831     }
832
833     tree_render();
834
835     // XXX: default reply for now, make this a better reply
836     return sstrdup("{\"success\": true}");
837 }
838
839 char *cmd_move_direction(Match *current_match, char *direction, char *move_px) {
840     // 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
841     int px = atoi(move_px);
842
843     /* TODO: make 'move' work with criteria. */
844     DLOG("moving in direction %s, px %s\n", direction, move_px);
845     if (con_is_floating(focused)) {
846         DLOG("floating move with %d pixels\n", px);
847         Rect newrect = focused->parent->rect;
848         if (strcmp(direction, "left") == 0) {
849             newrect.x -= px;
850         } else if (strcmp(direction, "right") == 0) {
851             newrect.x += px;
852         } else if (strcmp(direction, "up") == 0) {
853             newrect.y -= px;
854         } else if (strcmp(direction, "down") == 0) {
855             newrect.y += px;
856         }
857         floating_reposition(focused->parent, newrect);
858     } else {
859         tree_move((strcmp(direction, "right") == 0 ? TOK_RIGHT :
860                    (strcmp(direction, "left") == 0 ? TOK_LEFT :
861                     (strcmp(direction, "up") == 0 ? TOK_UP :
862                      TOK_DOWN))));
863         tree_render();
864     }
865
866
867     // XXX: default reply for now, make this a better reply
868     return sstrdup("{\"success\": true}");
869 }
870
871 char *cmd_layout(Match *current_match, char *layout_str) {
872     DLOG("changing layout to %s\n", layout_str);
873     owindow *current;
874     int layout = (strcmp(layout_str, "default") == 0 ? L_DEFAULT :
875                   (strcmp(layout_str, "stacked") == 0 || strcmp(layout_str, "stacking") == 0 ? L_STACKED :
876                    L_TABBED));
877
878     /* check if the match is empty, not if the result is empty */
879     if (match_is_empty(current_match))
880         con_set_layout(focused->parent, layout);
881     else {
882         TAILQ_FOREACH(current, &owindows, owindows) {
883             DLOG("matching: %p / %s\n", current->con, current->con->name);
884             con_set_layout(current->con, layout);
885         }
886     }
887
888     tree_render();
889
890     // XXX: default reply for now, make this a better reply
891     return sstrdup("{\"success\": true}");
892 }
893
894 char *cmd_exit(Match *current_match) {
895     LOG("Exiting due to user command.\n");
896     exit(0);
897
898     /* unreached */
899 }
900
901 char *cmd_reload(Match *current_match) {
902     LOG("reloading\n");
903     kill_configerror_nagbar(false);
904     load_configuration(conn, NULL, true);
905     x_set_i3_atoms();
906     /* Send an IPC event just in case the ws names have changed */
907     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
908
909     // XXX: default reply for now, make this a better reply
910     return sstrdup("{\"success\": true}");
911 }
912
913 char *cmd_restart(Match *current_match) {
914     LOG("restarting i3\n");
915     i3_restart(false);
916
917     // XXX: default reply for now, make this a better reply
918     return sstrdup("{\"success\": true}");
919 }
920
921 char *cmd_open(Match *current_match) {
922     LOG("opening new container\n");
923     Con *con = tree_open_con(NULL, NULL);
924     con_focus(con);
925     char *json_output;
926     sasprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
927
928     tree_render();
929
930     return json_output;
931 }
932
933 char *cmd_focus_output(Match *current_match, char *name) {
934     owindow *current;
935
936     DLOG("name = %s\n", name);
937
938     HANDLE_EMPTY_MATCH;
939
940     /* get the output */
941     Output *current_output = NULL;
942     Output *output;
943
944     TAILQ_FOREACH(current, &owindows, owindows)
945         current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
946     assert(current_output != NULL);
947
948     output = get_output_from_string(current_output, name);
949
950     if (!output) {
951         LOG("No such output found.\n");
952         return sstrdup("{\"sucess\": false}");
953     }
954
955     /* get visible workspace on output */
956     Con *ws = NULL;
957     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
958     if (!ws)
959         return sstrdup("{\"sucess\": false}");
960
961     workspace_show(ws);
962     tree_render();
963
964     // XXX: default reply for now, make this a better reply
965     return sstrdup("{\"success\": true}");
966 }
967
968 char *cmd_move_scratchpad(Match *current_match) {
969     DLOG("should move window to scratchpad\n");
970     owindow *current;
971
972     HANDLE_EMPTY_MATCH;
973
974     TAILQ_FOREACH(current, &owindows, owindows) {
975         DLOG("matching: %p / %s\n", current->con, current->con->name);
976         scratchpad_move(current->con);
977     }
978
979     tree_render();
980
981     // XXX: default reply for now, make this a better reply
982     return sstrdup("{\"success\": true}");
983 }
984
985 char *cmd_scratchpad_show(Match *current_match) {
986     DLOG("should show scratchpad window\n");
987     owindow *current;
988
989     if (match_is_empty(current_match)) {
990         scratchpad_show(NULL);
991     } else {
992         TAILQ_FOREACH(current, &owindows, owindows) {
993             DLOG("matching: %p / %s\n", current->con, current->con->name);
994             scratchpad_show(current->con);
995         }
996     }
997
998     tree_render();
999
1000     // XXX: default reply for now, make this a better reply
1001     return sstrdup("{\"success\": true}");
1002 }