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