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