3 * vim:ts=4:sw=4:expandtab
5 * i3 - an improved dynamic tiling window manager
6 * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
8 * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
12 #include <sys/types.h>
20 /** When the command did not include match criteria (!), we use the currently
21 * focused command. Do not confuse this case with a command which included
22 * criteria but which did not match any windows. This macro has to be called in
25 #define HANDLE_EMPTY_MATCH do { \
26 if (match_is_empty(¤t_match)) { \
27 owindow *ow = smalloc(sizeof(owindow)); \
29 TAILQ_INIT(&owindows); \
30 TAILQ_INSERT_TAIL(&owindows, ow, owindows); \
34 typedef struct yy_buffer_state *YY_BUFFER_STATE;
35 extern int cmdyylex(struct context *context);
36 extern int cmdyyparse(void);
37 extern int cmdyylex_destroy(void);
39 YY_BUFFER_STATE cmdyy_scan_string(const char *);
41 static struct context *context;
42 static Match current_match;
45 * Helper data structure for an operation window (window on which the operation
46 * will be performed). Used to build the TAILQ owindows.
49 typedef struct owindow {
51 TAILQ_ENTRY(owindow) owindows;
53 static TAILQ_HEAD(owindows_head, owindow) owindows;
55 /* Holds the JSON which will be returned via IPC or NULL for the default return
57 static char *json_output;
59 /* We don’t need yydebug for now, as we got decent error messages using
60 * yyerror(). Should you ever want to extend the parser, it might be handy
61 * to just comment it in again, so it stays here. */
64 void cmdyyerror(const char *error_message) {
66 ELOG("CMD: %s\n", error_message);
67 ELOG("CMD: in command:\n");
68 ELOG("CMD: %s\n", context->line_copy);
70 for (int c = 1; c <= context->last_column; c++)
71 if (c >= context->first_column)
76 context->compact_error = sstrdup(error_message);
83 char *parse_cmd(const char *new) {
85 LOG("COMMAND: *%s*\n", new);
86 cmdyy_scan_string(new);
88 match_init(¤t_match);
89 context = scalloc(sizeof(struct context));
90 context->filename = "cmd";
91 if (cmdyyparse() != 0) {
92 fprintf(stderr, "Could not parse command\n");
93 asprintf(&json_output, "{\"success\":false, \"error\":\"%s at position %d\"}",
94 context->compact_error, context->first_column);
95 FREE(context->line_copy);
96 FREE(context->compact_error);
100 printf("done, json output = %s\n", json_output);
103 FREE(context->line_copy);
104 FREE(context->compact_error);
110 * Returns true if a is definitely greater than b (using the given epsilon)
113 bool definitelyGreaterThan(float a, float b, float epsilon) {
114 return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
120 %lex-param { struct context *context }
128 %token TOK_EXEC "exec"
129 %token TOK_EXIT "exit"
130 %token TOK_RELOAD "reload"
131 %token TOK_RESTART "restart"
132 %token TOK_KILL "kill"
133 %token TOK_WINDOW "window"
134 %token TOK_CLIENT "client"
135 %token TOK_FULLSCREEN "fullscreen"
136 %token TOK_GLOBAL "global"
137 %token TOK_LAYOUT "layout"
138 %token TOK_DEFAULT "default"
139 %token TOK_STACKED "stacked"
140 %token TOK_TABBED "tabbed"
141 %token TOK_BORDER "border"
142 %token TOK_NORMAL "normal"
143 %token TOK_NONE "none"
144 %token TOK_1PIXEL "1pixel"
145 %token TOK_MODE "mode"
146 %token TOK_TILING "tiling"
147 %token TOK_FLOATING "floating"
148 %token TOK_MODE_TOGGLE "mode_toggle"
149 %token TOK_ENABLE "enable"
150 %token TOK_DISABLE "disable"
151 %token TOK_WORKSPACE "workspace"
152 %token TOK_TOGGLE "toggle"
153 %token TOK_FOCUS "focus"
154 %token TOK_MOVE "move"
155 %token TOK_OPEN "open"
156 %token TOK_NEXT "next"
157 %token TOK_PREV "prev"
158 %token TOK_SPLIT "split"
159 %token TOK_HORIZONTAL "horizontal"
160 %token TOK_VERTICAL "vertical"
162 %token TOK_DOWN "down"
163 %token TOK_LEFT "left"
164 %token TOK_RIGHT "right"
165 %token TOK_PARENT "parent"
166 %token TOK_CHILD "child"
167 %token TOK_APPEND_LAYOUT "append_layout"
168 %token TOK_MARK "mark"
169 %token TOK_RESIZE "resize"
170 %token TOK_GROW "grow"
171 %token TOK_SHRINK "shrink"
177 %token TOK_CLASS "class"
178 %token TOK_INSTANCE "instance"
180 %token TOK_CON_ID "con_id"
181 %token TOK_TITLE "title"
183 %token <string> STR "<string>"
184 %token <number> NUMBER "<number>"
186 %type <number> direction
187 %type <number> split_direction
188 %type <number> fullscreen_mode
190 %type <number> window_mode
191 %type <number> boolean
192 %type <number> border_style
193 %type <number> layout_mode
194 %type <number> resize_px
195 %type <number> resize_way
196 %type <number> resize_tiling
197 %type <number> optional_kill_mode
207 printf("single command completely parsed, dropping state...\n");
208 while (!TAILQ_EMPTY(&owindows)) {
209 current = TAILQ_FIRST(&owindows);
210 TAILQ_REMOVE(&owindows, current, owindows);
213 match_init(¤t_match);
222 | matchstart criteria matchend
224 printf("match parsed\n");
232 match_init(¤t_match);
233 TAILQ_INIT(&owindows);
236 TAILQ_FOREACH(con, &all_cons, all_cons) {
237 owindow *ow = smalloc(sizeof(owindow));
239 TAILQ_INSERT_TAIL(&owindows, ow, owindows);
247 owindow *next, *current;
249 printf("match specification finished, matching...\n");
250 /* copy the old list head to iterate through it and start with a fresh
251 * list which will contain only matching windows */
252 struct owindows_head old = owindows;
253 TAILQ_INIT(&owindows);
254 for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) {
255 /* make a copy of the next pointer and advance the pointer to the
256 * next element as we are going to invalidate the element’s
257 * next/prev pointers by calling TAILQ_INSERT_TAIL later */
259 next = TAILQ_NEXT(next, owindows);
261 printf("checking if con %p / %s matches\n", current->con, current->con->name);
262 if (current_match.con_id != NULL) {
263 if (current_match.con_id == current->con) {
264 printf("matches container!\n");
265 TAILQ_INSERT_TAIL(&owindows, current, owindows);
268 } else if (current_match.mark != NULL && current->con->mark != NULL &&
269 strcasecmp(current_match.mark, current->con->mark) == 0) {
270 printf("match by mark\n");
271 TAILQ_INSERT_TAIL(&owindows, current, owindows);
274 if (current->con->window == NULL)
276 if (match_matches_window(¤t_match, current->con->window)) {
277 printf("matches window!\n");
278 TAILQ_INSERT_TAIL(&owindows, current, owindows);
280 printf("doesnt match\n");
286 TAILQ_FOREACH(current, &owindows, owindows) {
287 printf("matching: %p / %s\n", current->con, current->con->name);
301 printf("criteria: class = %s\n", $3);
302 current_match.class = $3;
304 | TOK_INSTANCE '=' STR
306 printf("criteria: instance = %s\n", $3);
307 current_match.instance = $3;
311 printf("criteria: id = %s\n", $3);
313 long parsed = strtol($3, &end, 10);
314 if (parsed == LONG_MIN ||
315 parsed == LONG_MAX ||
317 (end && *end != '\0')) {
318 ELOG("Could not parse con id \"%s\"\n", $3);
320 current_match.con_id = (Con*)parsed;
321 printf("id as int = %p\n", current_match.con_id);
326 printf("criteria: window id = %s\n", $3);
328 long parsed = strtol($3, &end, 10);
329 if (parsed == LONG_MIN ||
330 parsed == LONG_MAX ||
332 (end && *end != '\0')) {
333 ELOG("Could not parse window id \"%s\"\n", $3);
335 current_match.id = parsed;
336 printf("window id as int = %d\n", current_match.id);
341 printf("criteria: mark = %s\n", $3);
342 current_match.mark = $3;
346 printf("criteria: title = %s\n", $3);
347 current_match.title = $3;
353 | operations ',' operation
381 printf("should execute %s\n", $2);
382 start_application($2);
390 printf("exit, bye bye\n");
398 printf("reloading\n");
399 kill_configerror_nagbar(false);
400 load_configuration(conn, NULL, true);
402 /* Send an IPC event just in case the ws names have changed */
403 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
410 printf("restarting i3\n");
420 if (match_is_empty(¤t_match)) {
421 ELOG("You have to specify which window/container should be focused.\n");
422 ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
424 asprintf(&json_output, "{\"success\":false, \"error\":\"You have to "
425 "specify which window/container should be focused\"}");
430 TAILQ_FOREACH(current, &owindows, owindows) {
431 Con *ws = con_get_workspace(current->con);
432 workspace_show(ws->name);
433 LOG("focusing %p / %s\n", current->con, current->con->name);
434 con_focus(current->con);
439 LOG("WARNING: Your criteria for the focus command matches %d containers, "
440 "while only exactly one container can be focused at a time.\n", count);
444 | TOK_FOCUS direction
449 LOG("Focusing left\n");
450 tree_next('p', HORIZ);
453 LOG("Focusing right\n");
454 tree_next('n', HORIZ);
457 LOG("Focusing up\n");
458 tree_next('p', VERT);
461 LOG("Focusing down\n");
462 tree_next('n', VERT);
465 ELOG("Invalid focus direction (%d)\n", direction);
471 | TOK_FOCUS window_mode
473 printf("should focus: ");
475 if ($2 == TOK_TILING)
477 else if ($2 == TOK_FLOATING)
478 printf("floating\n");
479 else printf("mode toggle\n");
481 Con *ws = con_get_workspace(focused);
485 if ($2 == TOK_MODE_TOGGLE) {
486 current = TAILQ_FIRST(&(ws->focus_head));
487 if (current != NULL && current->type == CT_FLOATING_CON)
488 to_focus = TOK_TILING;
489 else to_focus = TOK_FLOATING;
491 TAILQ_FOREACH(current, &(ws->focus_head), focused) {
492 if ((to_focus == TOK_FLOATING && current->type != CT_FLOATING_CON) ||
493 (to_focus == TOK_TILING && current->type == CT_FLOATING_CON))
496 con_focus(con_descend_focused(current));
505 if ($2 == TOK_PARENT)
514 TOK_TILING { $$ = TOK_TILING; }
515 | TOK_FLOATING { $$ = TOK_FLOATING; }
516 | TOK_MODE_TOGGLE { $$ = TOK_MODE_TOGGLE; }
520 TOK_PARENT { $$ = TOK_PARENT; }
521 | TOK_CHILD { $$ = TOK_CHILD; }
525 TOK_KILL optional_kill_mode
529 printf("killing!\n");
530 /* check if the match is empty, not if the result is empty */
531 if (match_is_empty(¤t_match))
534 TAILQ_FOREACH(current, &owindows, owindows) {
535 printf("matching: %p / %s\n", current->con, current->con->name);
536 tree_close(current->con, $2, false);
545 /* empty */ { $$ = KILL_WINDOW; }
546 | TOK_WINDOW { $$ = KILL_WINDOW; }
547 | TOK_CLIENT { $$ = KILL_CLIENT; }
551 TOK_WORKSPACE TOK_NEXT
556 | TOK_WORKSPACE TOK_PREV
563 printf("should switch to workspace %s\n", $2);
574 printf("opening new container\n");
575 Con *con = tree_open_con(NULL, NULL);
577 asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
584 TOK_FULLSCREEN fullscreen_mode
586 printf("toggling fullscreen, mode = %s\n", ($2 == CF_OUTPUT ? "normal" : "global"));
591 TAILQ_FOREACH(current, &owindows, owindows) {
592 printf("matching: %p / %s\n", current->con, current->con->name);
593 con_toggle_fullscreen(current->con, $2);
601 /* empty */ { $$ = CF_OUTPUT; }
602 | TOK_GLOBAL { $$ = CF_GLOBAL; }
606 TOK_SPLIT split_direction
608 /* TODO: use matches */
609 printf("splitting in direction %c\n", $2);
610 tree_split(focused, ($2 == 'v' ? VERT : HORIZ));
617 TOK_HORIZONTAL { $$ = 'h'; }
619 | TOK_VERTICAL { $$ = 'v'; }
629 TAILQ_FOREACH(current, &owindows, owindows) {
630 printf("matching: %p / %s\n", current->con, current->con->name);
631 if ($2 == TOK_TOGGLE) {
632 printf("should toggle mode\n");
633 toggle_floating_mode(current->con, false);
635 printf("should switch mode to %s\n", ($2 == TOK_FLOATING ? "floating" : "tiling"));
636 if ($2 == TOK_ENABLE) {
637 floating_enable(current->con, false);
639 floating_disable(current->con, false);
649 TOK_ENABLE { $$ = TOK_ENABLE; }
650 | TOK_DISABLE { $$ = TOK_DISABLE; }
651 | TOK_TOGGLE { $$ = TOK_TOGGLE; }
655 TOK_BORDER border_style
657 printf("border style should be changed to %d\n", $2);
662 TAILQ_FOREACH(current, &owindows, owindows) {
663 printf("matching: %p / %s\n", current->con, current->con->name);
664 if ($2 == TOK_TOGGLE) {
665 current->con->border_style++;
666 current->con->border_style %= 3;
667 } else current->con->border_style = $2;
675 TOK_NORMAL { $$ = BS_NORMAL; }
676 | TOK_NONE { $$ = BS_NONE; }
677 | TOK_1PIXEL { $$ = BS_1PIXEL; }
678 | TOK_TOGGLE { $$ = TOK_TOGGLE; }
684 printf("moving in direction %d\n", $2);
689 | TOK_MOVE TOK_WORKSPACE STR
693 printf("should move window to workspace %s\n", $3);
694 /* get the workspace */
695 Con *ws = workspace_get($3, NULL);
700 TAILQ_FOREACH(current, &owindows, owindows) {
701 printf("matching: %p / %s\n", current->con, current->con->name);
702 con_move_to_workspace(current->con, ws);
710 TOK_APPEND_LAYOUT STR
712 printf("restoring \"%s\"\n", $2);
713 tree_append_json($2);
720 TOK_LAYOUT layout_mode
722 printf("changing layout to %d\n", $2);
725 /* check if the match is empty, not if the result is empty */
726 if (match_is_empty(¤t_match))
727 con_set_layout(focused->parent, $2);
729 TAILQ_FOREACH(current, &owindows, owindows) {
730 printf("matching: %p / %s\n", current->con, current->con->name);
731 con_set_layout(current->con, $2);
740 TOK_DEFAULT { $$ = L_DEFAULT; }
741 | TOK_STACKED { $$ = L_STACKED; }
742 | TOK_TABBED { $$ = L_TABBED; }
748 printf("marking window with str %s\n", $2);
753 TAILQ_FOREACH(current, &owindows, owindows) {
754 printf("matching: %p / %s\n", current->con, current->con->name);
755 current->con->mark = sstrdup($2);
767 printf("-------------------------------------------------\n");
768 printf(" NOP: %s\n", $2);
769 printf("-------------------------------------------------\n");
775 TOK_RESIZE resize_way direction resize_px resize_tiling
777 /* resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt] */
778 printf("resizing in way %d, direction %d, px %d or ppt %d\n", $2, $3, $4, $5);
782 if ($2 == TOK_SHRINK) {
787 if (con_is_floating(focused)) {
788 printf("floating resize\n");
789 if (direction == TOK_UP) {
790 focused->parent->rect.y -= px;
791 focused->parent->rect.height += px;
792 } else if (direction == TOK_DOWN) {
793 focused->parent->rect.height += px;
794 } else if (direction == TOK_LEFT) {
795 focused->parent->rect.x -= px;
796 focused->parent->rect.width += px;
798 focused->parent->rect.width += px;
801 LOG("tiling resize\n");
802 /* get the default percentage */
803 int children = con_num_children(focused->parent);
805 LOG("ins. %d children\n", children);
806 double percentage = 1.0 / children;
807 LOG("default percentage = %f\n", percentage);
809 if (direction == TOK_UP || direction == TOK_LEFT) {
810 other = TAILQ_PREV(focused, nodes_head, nodes);
812 other = TAILQ_NEXT(focused, nodes);
814 if (other == TAILQ_END(workspaces)) {
815 LOG("No other container in this direction found, cannot resize.\n");
818 LOG("other->percent = %f\n", other->percent);
819 LOG("focused->percent before = %f\n", focused->percent);
820 if (focused->percent == 0.0)
821 focused->percent = percentage;
822 if (other->percent == 0.0)
823 other->percent = percentage;
824 double new_focused_percent = focused->percent + ((double)ppt / 100.0);
825 double new_other_percent = other->percent - ((double)ppt / 100.0);
826 LOG("new_focused_percent = %f\n", new_focused_percent);
827 LOG("new_other_percent = %f\n", new_other_percent);
828 /* Ensure that the new percentages are positive and greater than
829 * 0.05 to have a reasonable minimum size. */
830 if (definitelyGreaterThan(new_focused_percent, 0.05, DBL_EPSILON) &&
831 definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) {
832 focused->percent += ((double)ppt / 100.0);
833 other->percent -= ((double)ppt / 100.0);
834 LOG("focused->percent after = %f\n", focused->percent);
835 LOG("other->percent after = %f\n", other->percent);
837 LOG("Not resizing, already at minimum size\n");
861 | TOK_OR NUMBER TOK_PPT
868 TOK_GROW { $$ = TOK_GROW; }
869 | TOK_SHRINK { $$ = TOK_SHRINK; }
873 TOK_UP { $$ = TOK_UP; }
874 | TOK_DOWN { $$ = TOK_DOWN; }
875 | TOK_LEFT { $$ = TOK_LEFT; }
876 | TOK_RIGHT { $$ = TOK_RIGHT; }