]> git.sur5r.net Git - i3/i3/blob - src/cmdparse.y
Make workspace_layout handle all cons at workspace level, not only the first one...
[i3/i3] / src / cmdparse.y
1 %{
2 /*
3  * vim:ts=4:sw=4:expandtab
4  *
5  * i3 - an improved dynamic tiling window manager
6  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
7  *
8  * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
9  *
10
11  */
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 #include <fcntl.h>
16 #include <limits.h>
17
18 #include "all.h"
19
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
23  * every command.
24  */
25 #define HANDLE_EMPTY_MATCH do { \
26     if (match_is_empty(&current_match)) { \
27         owindow *ow = smalloc(sizeof(owindow)); \
28         ow->con = focused; \
29         TAILQ_INIT(&owindows); \
30         TAILQ_INSERT_TAIL(&owindows, ow, owindows); \
31     } \
32 } while (0)
33
34 typedef struct yy_buffer_state *YY_BUFFER_STATE;
35 extern int cmdyylex(struct context *context);
36 extern int cmdyyparse(void);
37 extern FILE *cmdyyin;
38 YY_BUFFER_STATE cmdyy_scan_string(const char *);
39
40 static struct context *context;
41 static Match current_match;
42
43 /*
44  * Helper data structure for an operation window (window on which the operation
45  * will be performed). Used to build the TAILQ owindows.
46  *
47  */
48 typedef struct owindow {
49     Con *con;
50     TAILQ_ENTRY(owindow) owindows;
51 } owindow;
52 static TAILQ_HEAD(owindows_head, owindow) owindows;
53
54 /* Holds the JSON which will be returned via IPC or NULL for the default return
55  * message */
56 static char *json_output;
57
58 /* We don’t need yydebug for now, as we got decent error messages using
59  * yyerror(). Should you ever want to extend the parser, it might be handy
60  * to just comment it in again, so it stays here. */
61 //int cmdyydebug = 1;
62
63 void cmdyyerror(const char *error_message) {
64     ELOG("\n");
65     ELOG("CMD: %s\n", error_message);
66     ELOG("CMD: in command:\n");
67     ELOG("CMD:   %s\n", context->line_copy);
68     ELOG("CMD:   ");
69     for (int c = 1; c <= context->last_column; c++)
70         if (c >= context->first_column)
71                 printf("^");
72         else printf(" ");
73     printf("\n");
74     ELOG("\n");
75     context->compact_error = sstrdup(error_message);
76 }
77
78 int cmdyywrap() {
79     return 1;
80 }
81
82 char *parse_cmd(const char *new) {
83     LOG("COMMAND: *%s*\n", new);
84     cmdyy_scan_string(new);
85
86     match_init(&current_match);
87     context = scalloc(sizeof(struct context));
88     context->filename = "cmd";
89     FREE(json_output);
90     if (cmdyyparse() != 0) {
91         fprintf(stderr, "Could not parse command\n");
92         asprintf(&json_output, "{\"success\":false, \"error\":\"%s at position %d\"}",
93                  context->compact_error, context->first_column);
94         FREE(context->line_copy);
95         FREE(context->compact_error);
96         free(context);
97         return json_output;
98     }
99     printf("done, json output = %s\n", json_output);
100
101     FREE(context->line_copy);
102     FREE(context->compact_error);
103     free(context);
104     return json_output;
105 }
106
107 %}
108
109 %error-verbose
110 %lex-param { struct context *context }
111
112 %union {
113     char *string;
114     char chr;
115     int number;
116 }
117
118 %token              TOK_EXEC            "exec"
119 %token              TOK_EXIT            "exit"
120 %token              TOK_RELOAD          "reload"
121 %token              TOK_RESTART         "restart"
122 %token              TOK_KILL            "kill"
123 %token              TOK_WINDOW          "window"
124 %token              TOK_CLIENT          "client"
125 %token              TOK_FULLSCREEN      "fullscreen"
126 %token              TOK_GLOBAL          "global"
127 %token              TOK_LAYOUT          "layout"
128 %token              TOK_DEFAULT         "default"
129 %token              TOK_STACKED         "stacked"
130 %token              TOK_TABBED          "tabbed"
131 %token              TOK_BORDER          "border"
132 %token              TOK_NORMAL          "normal"
133 %token              TOK_NONE            "none"
134 %token              TOK_1PIXEL          "1pixel"
135 %token              TOK_MODE            "mode"
136 %token              TOK_TILING          "tiling"
137 %token              TOK_FLOATING        "floating"
138 %token              TOK_WORKSPACE       "workspace"
139 %token              TOK_TOGGLE          "toggle"
140 %token              TOK_FOCUS           "focus"
141 %token              TOK_MOVE            "move"
142 %token              TOK_OPEN            "open"
143 %token              TOK_NEXT            "next"
144 %token              TOK_PREV            "prev"
145 %token              TOK_SPLIT           "split"
146 %token              TOK_HORIZONTAL      "horizontal"
147 %token              TOK_VERTICAL        "vertical"
148 %token              TOK_LEVEL           "level"
149 %token              TOK_UP              "up"
150 %token              TOK_DOWN            "down"
151 %token              TOK_LEFT            "left"
152 %token              TOK_RIGHT           "right"
153 %token              TOK_RESTORE         "restore"
154 %token              TOK_MARK            "mark"
155 %token              TOK_RESIZE          "resize"
156 %token              TOK_GROW            "grow"
157 %token              TOK_SHRINK          "shrink"
158 %token              TOK_PX              "px"
159 %token              TOK_OR              "or"
160 %token              TOK_PPT             "ppt"
161 %token              TOK_NOP             "nop"
162
163 %token              TOK_CLASS           "class"
164 %token              TOK_ID              "id"
165 %token              TOK_CON_ID          "con_id"
166
167 %token  <string>    STR                 "<string>"
168 %token  <number>    NUMBER              "<number>"
169
170 %type   <number>    direction
171 %type   <chr>       level_direction
172 %type   <number>    window_mode
173 %type   <number>    border_style
174 %type   <number>    layout_mode
175 %type   <number>    resize_px
176 %type   <number>    resize_way
177 %type   <number>    resize_tiling
178 %type   <number>    optional_kill_mode
179
180 %%
181
182 commands:
183     commands ';' command
184     | command
185     {
186         owindow *current;
187
188         printf("single command completely parsed, dropping state...\n");
189         while (!TAILQ_EMPTY(&owindows)) {
190             current = TAILQ_FIRST(&owindows);
191             TAILQ_REMOVE(&owindows, current, owindows);
192             free(current);
193         }
194         match_init(&current_match);
195     }
196     ;
197
198 command:
199     match operations
200     ;
201
202 match:
203     | matchstart criteria matchend
204     {
205         printf("match parsed\n");
206     }
207     ;
208
209 matchstart:
210     '['
211     {
212         printf("start\n");
213         match_init(&current_match);
214         TAILQ_INIT(&owindows);
215         /* copy all_cons */
216         Con *con;
217         TAILQ_FOREACH(con, &all_cons, all_cons) {
218             owindow *ow = smalloc(sizeof(owindow));
219             ow->con = con;
220             TAILQ_INSERT_TAIL(&owindows, ow, owindows);
221         }
222     }
223     ;
224
225 matchend:
226     ']'
227     {
228         owindow *next, *current;
229
230         printf("match specification finished, matching...\n");
231         /* copy the old list head to iterate through it and start with a fresh
232          * list which will contain only matching windows */
233         struct owindows_head old = owindows;
234         TAILQ_INIT(&owindows);
235         for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) {
236             /* make a copy of the next pointer and advance the pointer to the
237              * next element as we are going to invalidate the element’s
238              * next/prev pointers by calling TAILQ_INSERT_TAIL later */
239             current = next;
240             next = TAILQ_NEXT(next, owindows);
241
242             printf("checking if con %p / %s matches\n", current->con, current->con->name);
243             if (current_match.con_id != NULL) {
244                 if (current_match.con_id == current->con) {
245                     printf("matches container!\n");
246                     TAILQ_INSERT_TAIL(&owindows, current, owindows);
247
248                 }
249             } else if (current_match.mark != NULL && current->con->mark != NULL &&
250                     strcasecmp(current_match.mark, current->con->mark) == 0) {
251                 printf("match by mark\n");
252                     TAILQ_INSERT_TAIL(&owindows, current, owindows);
253
254             } else {
255                 if (current->con->window == NULL)
256                     continue;
257                 if (match_matches_window(&current_match, current->con->window)) {
258                     printf("matches window!\n");
259                     TAILQ_INSERT_TAIL(&owindows, current, owindows);
260                 } else {
261                     printf("doesnt match\n");
262                     free(current);
263                 }
264             }
265         }
266
267         TAILQ_FOREACH(current, &owindows, owindows) {
268             printf("matching: %p / %s\n", current->con, current->con->name);
269         }
270
271     }
272     ;
273
274 criteria:
275     TOK_CLASS '=' STR
276     {
277         printf("criteria: class = %s\n", $3);
278         current_match.class = $3;
279     }
280     | TOK_CON_ID '=' STR
281     {
282         printf("criteria: id = %s\n", $3);
283         char *end;
284         long parsed = strtol($3, &end, 10);
285         if (parsed == LONG_MIN ||
286             parsed == LONG_MAX ||
287             parsed < 0 ||
288             (end && *end != '\0')) {
289             ELOG("Could not parse con id \"%s\"\n", $3);
290         } else {
291             current_match.con_id = (Con*)parsed;
292             printf("id as int = %p\n", current_match.con_id);
293         }
294     }
295     | TOK_ID '=' STR
296     {
297         printf("criteria: window id = %s\n", $3);
298         char *end;
299         long parsed = strtol($3, &end, 10);
300         if (parsed == LONG_MIN ||
301             parsed == LONG_MAX ||
302             parsed < 0 ||
303             (end && *end != '\0')) {
304             ELOG("Could not parse window id \"%s\"\n", $3);
305         } else {
306             current_match.id = parsed;
307             printf("window id as int = %d\n", current_match.id);
308         }
309     }
310     | TOK_MARK '=' STR
311     {
312         printf("criteria: mark = %s\n", $3);
313         current_match.mark = $3;
314     }
315     ;
316
317 operations:
318     operation
319     | operations ',' operation
320     ;
321
322 operation:
323     exec
324     | exit
325     | restart
326     | reload
327     | border
328     | layout
329     | restore
330     | move
331     | workspace
332     | focus
333     | kill
334     | open
335     | fullscreen
336     | next
337     | prev
338     | split
339     | mode
340     | level
341     | mark
342     | resize
343     | nop
344     ;
345
346 exec:
347     TOK_EXEC STR
348     {
349         printf("should execute %s\n", $2);
350         start_application($2);
351         free($2);
352     }
353     ;
354
355 exit:
356     TOK_EXIT
357     {
358         printf("exit, bye bye\n");
359         exit(0);
360     }
361     ;
362
363 reload:
364     TOK_RELOAD
365     {
366         printf("reloading\n");
367         load_configuration(conn, NULL, true);
368         x_set_i3_atoms();
369         /* Send an IPC event just in case the ws names have changed */
370         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
371     }
372     ;
373
374 restart:
375     TOK_RESTART
376     {
377         printf("restarting i3\n");
378         i3_restart(false);
379     }
380     ;
381
382 focus:
383     TOK_FOCUS
384     {
385         owindow *current;
386
387         printf("should focus\n");
388         if (match_is_empty(&current_match)) {
389             /* TODO: better error message */
390             LOG("Error: The focus command requires you to use some criteria.\n");
391             break;
392         }
393
394         /* TODO: warning if the match contains more than one entry. does not
395          * make so much sense when focusing */
396         TAILQ_FOREACH(current, &owindows, owindows) {
397             LOG("focusing %p / %s\n", current->con, current->con->name);
398             con_focus(current->con);
399         }
400
401         tree_render();
402     }
403     ;
404
405 kill:
406     TOK_KILL optional_kill_mode
407     {
408         owindow *current;
409
410         printf("killing!\n");
411         /* check if the match is empty, not if the result is empty */
412         if (match_is_empty(&current_match))
413             tree_close_con($2);
414         else {
415             TAILQ_FOREACH(current, &owindows, owindows) {
416                 printf("matching: %p / %s\n", current->con, current->con->name);
417                 tree_close(current->con, $2, false);
418             }
419         }
420
421         tree_render();
422     }
423     ;
424
425 optional_kill_mode:
426     /* empty */             { $$ = KILL_WINDOW; }
427     | TOK_WINDOW { $$ = KILL_WINDOW; }
428     | TOK_CLIENT { $$ = KILL_CLIENT; }
429     ;
430
431 workspace:
432     TOK_WORKSPACE STR
433     {
434         printf("should switch to workspace %s\n", $2);
435         workspace_show($2);
436         free($2);
437
438         tree_render();
439     }
440     ;
441
442 open:
443     TOK_OPEN
444     {
445         printf("opening new container\n");
446         Con *con = tree_open_con(NULL, NULL);
447         con_focus(con);
448         asprintf(&json_output, "{\"success\":true, \"id\":%ld}", (long int)con);
449
450         tree_render();
451     }
452     ;
453
454 fullscreen:
455     TOK_FULLSCREEN
456     {
457         printf("toggling fullscreen\n");
458         owindow *current;
459
460
461         HANDLE_EMPTY_MATCH;
462
463         TAILQ_FOREACH(current, &owindows, owindows) {
464             printf("matching: %p / %s\n", current->con, current->con->name);
465             con_toggle_fullscreen(current->con);
466         }
467
468         tree_render();
469     }
470     ;
471
472 next:
473     TOK_NEXT direction
474     {
475         /* TODO: use matches */
476         printf("should select next window in direction %c\n", $2);
477         tree_next('n', ($2 == 'v' ? VERT : HORIZ));
478
479         tree_render();
480     }
481     ;
482
483 prev:
484     TOK_PREV direction
485     {
486         /* TODO: use matches */
487         printf("should select prev window in direction %c\n", $2);
488         tree_next('p', ($2 == 'v' ? VERT : HORIZ));
489
490         tree_render();
491     }
492     ;
493
494 split:
495     TOK_SPLIT direction
496     {
497         /* TODO: use matches */
498         printf("splitting in direction %c\n", $2);
499         tree_split(focused, ($2 == 'v' ? VERT : HORIZ));
500
501         tree_render();
502     }
503     ;
504
505 direction:
506     TOK_HORIZONTAL  { $$ = 'h'; }
507     | 'h'           { $$ = 'h'; }
508     | TOK_VERTICAL  { $$ = 'v'; }
509     | 'v'           { $$ = 'v'; }
510     ;
511
512 mode:
513     TOK_MODE window_mode
514     {
515         HANDLE_EMPTY_MATCH;
516
517         owindow *current;
518         TAILQ_FOREACH(current, &owindows, owindows) {
519             printf("matching: %p / %s\n", current->con, current->con->name);
520             if ($2 == TOK_TOGGLE) {
521                 printf("should toggle mode\n");
522                 toggle_floating_mode(current->con, false);
523             } else {
524                 printf("should switch mode to %s\n", ($2 == TOK_FLOATING ? "floating" : "tiling"));
525                 if ($2 == TOK_FLOATING) {
526                     floating_enable(current->con, false);
527                 } else {
528                     floating_disable(current->con, false);
529                 }
530             }
531         }
532
533         tree_render();
534     }
535     ;
536
537 window_mode:
538     TOK_FLOATING    { $$ = TOK_FLOATING; }
539     | TOK_TILING    { $$ = TOK_TILING; }
540     | TOK_TOGGLE    { $$ = TOK_TOGGLE; }
541     ;
542
543 border:
544     TOK_BORDER border_style
545     {
546         printf("border style should be changed to %d\n", $2);
547         owindow *current;
548
549         HANDLE_EMPTY_MATCH;
550
551         TAILQ_FOREACH(current, &owindows, owindows) {
552             printf("matching: %p / %s\n", current->con, current->con->name);
553             current->con->border_style = $2;
554         }
555
556         tree_render();
557     }
558     ;
559
560 border_style:
561     TOK_NORMAL      { $$ = BS_NORMAL; }
562     | TOK_NONE      { $$ = BS_NONE; }
563     | TOK_1PIXEL    { $$ = BS_1PIXEL; }
564     ;
565
566
567 level:
568     TOK_LEVEL level_direction
569     {
570         printf("level %c\n", $2);
571         if ($2 == 'u')
572             level_up();
573         else level_down();
574
575         tree_render();
576     }
577     ;
578
579 level_direction:
580     TOK_UP     { $$ = 'u'; }
581     | TOK_DOWN { $$ = 'd'; }
582     ;
583
584 move:
585     TOK_MOVE direction
586     {
587         printf("moving in direction %d\n", $2);
588         tree_move($2);
589
590         tree_render();
591     }
592     | TOK_MOVE TOK_WORKSPACE STR
593     {
594         owindow *current;
595
596         printf("should move window to workspace %s\n", $3);
597         /* get the workspace */
598         Con *ws = workspace_get($3, NULL);
599         free($3);
600
601         HANDLE_EMPTY_MATCH;
602
603         TAILQ_FOREACH(current, &owindows, owindows) {
604             printf("matching: %p / %s\n", current->con, current->con->name);
605             con_move_to_workspace(current->con, ws);
606         }
607
608         tree_render();
609     }
610     ;
611
612 restore:
613     TOK_RESTORE STR
614     {
615         printf("restoring \"%s\"\n", $2);
616         tree_append_json($2);
617         free($2);
618         tree_render();
619     }
620     ;
621
622 layout:
623     TOK_LAYOUT layout_mode
624     {
625         printf("changing layout to %d\n", $2);
626         owindow *current;
627
628         /* check if the match is empty, not if the result is empty */
629         if (match_is_empty(&current_match))
630             con_set_layout(focused->parent, $2);
631         else {
632             TAILQ_FOREACH(current, &owindows, owindows) {
633                 printf("matching: %p / %s\n", current->con, current->con->name);
634                 con_set_layout(current->con, $2);
635             }
636         }
637
638         tree_render();
639     }
640     ;
641
642 layout_mode:
643     TOK_DEFAULT   { $$ = L_DEFAULT; }
644     | TOK_STACKED { $$ = L_STACKED; }
645     | TOK_TABBED  { $$ = L_TABBED; }
646     ;
647
648 mark:
649     TOK_MARK STR
650     {
651         printf("marking window with str %s\n", $2);
652         owindow *current;
653
654         HANDLE_EMPTY_MATCH;
655
656         TAILQ_FOREACH(current, &owindows, owindows) {
657             printf("matching: %p / %s\n", current->con, current->con->name);
658             current->con->mark = sstrdup($2);
659         }
660
661         free($<string>2);
662
663         tree_render();
664     }
665     ;
666
667 nop:
668     TOK_NOP STR
669     {
670         printf("-------------------------------------------------\n");
671         printf("  NOP: %s\n", $2);
672         printf("-------------------------------------------------\n");
673         free($2);
674     }
675     ;
676
677 resize:
678     TOK_RESIZE resize_way direction resize_px resize_tiling
679     {
680         /* resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt] */
681         printf("resizing in way %d, direction %d, px %d or ppt %d\n", $2, $3, $4, $5);
682         int direction = $3;
683         int px = $4;
684         int ppt = $5;
685         if ($2 == TOK_SHRINK) {
686             px *= -1;
687             ppt *= -1;
688         }
689
690         if (con_is_floating(focused)) {
691             printf("floating resize\n");
692             if (direction == TOK_UP) {
693                 focused->parent->rect.y -= px;
694                 focused->parent->rect.height += px;
695             } else if (direction == TOK_DOWN) {
696                 focused->rect.height += px;
697             } else if (direction == TOK_LEFT) {
698                 focused->rect.x -= px;
699                 focused->rect.width += px;
700             } else {
701                 focused->rect.width += px;
702             }
703         } else {
704             LOG("tiling resize\n");
705             /* get the default percentage */
706             int children = con_num_children(focused->parent);
707             Con *other;
708             LOG("ins. %d children\n", children);
709             double percentage = 1.0 / children;
710             LOG("default percentage = %f\n", percentage);
711
712             if (direction == TOK_UP || direction == TOK_LEFT) {
713                 other = TAILQ_PREV(focused, nodes_head, nodes);
714             } else {
715                 other = TAILQ_NEXT(focused, nodes);
716             }
717             if (other == TAILQ_END(workspaces)) {
718                 LOG("No other container in this direction found, cannot resize.\n");
719                 return 0;
720             }
721             LOG("other->percent = %f\n", other->percent);
722             LOG("focused->percent before = %f\n", focused->percent);
723             if (focused->percent == 0.0)
724                 focused->percent = percentage;
725             if (other->percent == 0.0)
726                 other->percent = percentage;
727             focused->percent += ((double)ppt / 100.0);
728             other->percent -= ((double)ppt / 100.0);
729             LOG("focused->percent after = %f\n", focused->percent);
730             LOG("other->percent after = %f\n", other->percent);
731         }
732
733         tree_render();
734     }
735     ;
736
737 resize_px:
738     /* empty */
739     {
740         $$ = 10;
741     }
742     | NUMBER TOK_PX
743     {
744         $$ = $1;
745     }
746     ;
747
748 resize_tiling:
749     /* empty */
750     {
751         $$ = 10;
752     }
753     | TOK_OR NUMBER TOK_PPT
754     {
755         $$ = $2;
756     }
757     ;
758
759 resize_way:
760     TOK_GROW        { $$ = TOK_GROW; }
761     | TOK_SHRINK    { $$ = TOK_SHRINK; }
762     ;
763
764 direction:
765     TOK_UP          { $$ = TOK_UP; }
766     | TOK_DOWN      { $$ = TOK_DOWN; }
767     | TOK_LEFT      { $$ = TOK_LEFT; }
768     | TOK_RIGHT     { $$ = TOK_RIGHT; }
769     ;