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