]> git.sur5r.net Git - i3/i3/blob - src/cfgparse.y
Implement support for PCRE regular expressions for all criteria (for_window, commands...
[i3/i3] / src / cfgparse.y
1 %{
2 /*
3  * vim:ts=4:sw=4:expandtab
4  *
5  */
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <sys/wait.h>
9 #include <unistd.h>
10 #include <fcntl.h>
11
12 #include "all.h"
13
14 static pid_t configerror_pid = -1;
15
16 static Match current_match;
17
18 typedef struct yy_buffer_state *YY_BUFFER_STATE;
19 extern int yylex(struct context *context);
20 extern int yyparse(void);
21 extern int yylex_destroy(void);
22 extern FILE *yyin;
23 YY_BUFFER_STATE yy_scan_string(const char *);
24
25 static struct bindings_head *current_bindings;
26 static struct context *context;
27
28 /* We don’t need yydebug for now, as we got decent error messages using
29  * yyerror(). Should you ever want to extend the parser, it might be handy
30  * to just comment it in again, so it stays here. */
31 //int yydebug = 1;
32
33 void yyerror(const char *error_message) {
34     context->has_errors = true;
35
36     ELOG("\n");
37     ELOG("CONFIG: %s\n", error_message);
38     ELOG("CONFIG: in file \"%s\", line %d:\n",
39         context->filename, context->line_number);
40     ELOG("CONFIG:   %s\n", context->line_copy);
41     char buffer[context->last_column+1];
42     buffer[context->last_column] = '\0';
43     for (int c = 1; c <= context->last_column; c++)
44         buffer[c-1] = (c >= context->first_column ? '^' : ' ');
45     ELOG("CONFIG:   %s\n", buffer);
46     ELOG("\n");
47 }
48
49 int yywrap() {
50     return 1;
51 }
52
53 /*
54  * Goes through each line of buf (separated by \n) and checks for statements /
55  * commands which only occur in i3 v4 configuration files. If it finds any, it
56  * returns version 4, otherwise it returns version 3.
57  *
58  */
59 static int detect_version(char *buf) {
60     char *walk = buf;
61     char *line = buf;
62     while (*walk != '\0') {
63         if (*walk != '\n') {
64             walk++;
65             continue;
66         }
67
68         /* check for some v4-only statements */
69         if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
70             strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
71             strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
72             strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
73             printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
74             return 4;
75         }
76
77         /* if this is a bind statement, we can check the command */
78         if (strncasecmp(line, "bind", strlen("bind")) == 0) {
79             char *bind = strchr(line, ' ');
80             if (bind == NULL)
81                 goto next;
82             while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
83                 bind++;
84             if (*bind == '\0')
85                 goto next;
86             if ((bind = strchr(bind, ' ')) == NULL)
87                 goto next;
88             while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
89                 bind++;
90             if (*bind == '\0')
91                 goto next;
92             if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
93                 strncasecmp(bind, "floating", strlen("floating")) == 0 ||
94                 strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
95                 strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
96                 strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
97                 strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
98                 strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
99                 strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
100                 strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
101                 strncasecmp(bind, "border borderless", strlen("border borderless")) == 0) {
102                 printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
103                 return 4;
104             }
105         }
106
107 next:
108         /* advance to the next line */
109         walk++;
110         line = walk;
111     }
112
113     return 3;
114 }
115
116 /*
117  * Calls i3-migrate-config-to-v4 to migrate a configuration file (input
118  * buffer).
119  *
120  * Returns the converted config file or NULL if there was an error (for
121  * example the script could not be found in $PATH or the i3 executable’s
122  * directory).
123  *
124  */
125 static char *migrate_config(char *input, off_t size) {
126     int writepipe[2];
127     int readpipe[2];
128
129     if (pipe(writepipe) != 0 ||
130         pipe(readpipe) != 0) {
131         warn("migrate_config: Could not create pipes");
132         return NULL;
133     }
134
135     pid_t pid = fork();
136     if (pid == -1) {
137         warn("Could not fork()");
138         return NULL;
139     }
140
141     /* child */
142     if (pid == 0) {
143         /* close writing end of writepipe, connect reading side to stdin */
144         close(writepipe[1]);
145         dup2(writepipe[0], 0);
146
147         /* close reading end of readpipe, connect writing side to stdout */
148         close(readpipe[0]);
149         dup2(readpipe[1], 1);
150
151         static char *argv[] = {
152             NULL, /* will be replaced by the executable path */
153             NULL
154         };
155         exec_i3_utility("i3-migrate-config-to-v4", argv);
156     }
157
158     /* parent */
159
160     /* close reading end of the writepipe (connected to the script’s stdin) */
161     close(writepipe[0]);
162
163     /* write the whole config file to the pipe, the script will read everything
164      * immediately */
165     int written = 0;
166     int ret;
167     while (written < size) {
168         if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
169             warn("Could not write to pipe");
170             return NULL;
171         }
172         written += ret;
173     }
174     close(writepipe[1]);
175
176     /* close writing end of the readpipe (connected to the script’s stdout) */
177     close(readpipe[1]);
178
179     /* read the script’s output */
180     int conv_size = 65535;
181     char *converted = malloc(conv_size);
182     int read_bytes = 0;
183     do {
184         if (read_bytes == conv_size) {
185             conv_size += 65535;
186             converted = realloc(converted, conv_size);
187         }
188         ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
189         if (ret == -1) {
190             warn("Cannot read from pipe");
191             FREE(converted);
192             return NULL;
193         }
194         read_bytes += ret;
195     } while (ret > 0);
196
197     /* get the returncode */
198     int status;
199     wait(&status);
200     if (!WIFEXITED(status)) {
201         fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
202         return NULL;
203     }
204
205     int returncode = WEXITSTATUS(status);
206     if (returncode != 0) {
207         fprintf(stderr, "Migration process exit code was != 0\n");
208         if (returncode == 2) {
209             fprintf(stderr, "could not start the migration script\n");
210             /* TODO: script was not found. tell the user to fix his system or create a v4 config */
211         } else if (returncode == 1) {
212             fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
213             fprintf(stderr, "# i3 config file (v4)\n");
214             /* TODO: nag the user with a message to include a hint for i3 in his config file */
215         }
216         return NULL;
217     }
218
219     return converted;
220 }
221
222 /*
223  * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
224  * it exited (or could not be started, depending on the exit code).
225  *
226  */
227 static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
228     ev_child_stop(EV_A_ watcher);
229     if (!WIFEXITED(watcher->rstatus)) {
230         fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
231         return;
232     }
233
234     int exitcode = WEXITSTATUS(watcher->rstatus);
235     printf("i3-nagbar process exited with status %d\n", exitcode);
236     if (exitcode == 2) {
237         fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
238     }
239
240     configerror_pid = -1;
241 }
242
243 /*
244  * Starts an i3-nagbar process which alerts the user that his configuration
245  * file contains one or more errors. Also offers two buttons: One to launch an
246  * $EDITOR on the config file and another one to launch a $PAGER on the error
247  * logfile.
248  *
249  */
250 static void start_configerror_nagbar(const char *config_path) {
251     fprintf(stderr, "Would start i3-nagscreen now\n");
252     configerror_pid = fork();
253     if (configerror_pid == -1) {
254         warn("Could not fork()");
255         return;
256     }
257
258     /* child */
259     if (configerror_pid == 0) {
260         char *editaction,
261              *pageraction;
262         if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\" && i3-msg reload\"", config_path) == -1)
263             exit(1);
264         if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1)
265             exit(1);
266         char *argv[] = {
267             NULL, /* will be replaced by the executable path */
268             "-m",
269             "You have an error in your i3 config file!",
270             "-b",
271             "edit config",
272             editaction,
273             (errorfilename ? "-b" : NULL),
274             "show errors",
275             pageraction,
276             NULL
277         };
278         exec_i3_utility("i3-nagbar", argv);
279     }
280
281     /* parent */
282     /* install a child watcher */
283     ev_child *child = smalloc(sizeof(ev_child));
284     ev_child_init(child, &nagbar_exited, configerror_pid, 0);
285     ev_child_start(main_loop, child);
286 }
287
288 /*
289  * Kills the configerror i3-nagbar process, if any.
290  *
291  * Called when reloading/restarting.
292  *
293  * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
294  * ev is assumed to handle it (reloading).
295  *
296  */
297 void kill_configerror_nagbar(bool wait_for_it) {
298     if (configerror_pid == -1)
299         return;
300
301     if (kill(configerror_pid, SIGTERM) == -1)
302         warn("kill(configerror_nagbar) failed");
303
304     if (!wait_for_it)
305         return;
306
307     /* When restarting, we don’t enter the ev main loop anymore and after the
308      * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
309      * for us and we would end up with a <defunct> process. Therefore we
310      * waitpid() here. */
311     waitpid(configerror_pid, NULL, 0);
312 }
313
314 /*
315  * Checks for duplicate key bindings (the same keycode or keysym is configured
316  * more than once). If a duplicate binding is found, a message is printed to
317  * stderr and the has_errors variable is set to true, which will start
318  * i3-nagbar.
319  *
320  */
321 static void check_for_duplicate_bindings(struct context *context) {
322     Binding *bind, *current;
323     TAILQ_FOREACH(current, bindings, bindings) {
324         TAILQ_FOREACH(bind, bindings, bindings) {
325             /* Abort when we reach the current keybinding, only check the
326              * bindings before */
327             if (bind == current)
328                 break;
329
330             /* Check if one is using keysym while the other is using bindsym.
331              * If so, skip. */
332             /* XXX: It should be checked at a later place (when translating the
333              * keysym to keycodes) if there are any duplicates */
334             if ((bind->symbol == NULL && current->symbol != NULL) ||
335                 (bind->symbol != NULL && current->symbol == NULL))
336                 continue;
337
338             /* If bind is NULL, current has to be NULL, too (see above).
339              * If the keycodes differ, it can't be a duplicate. */
340             if (bind->symbol != NULL &&
341                 strcasecmp(bind->symbol, current->symbol) != 0)
342                 continue;
343
344             /* Check if the keycodes or modifiers are different. If so, they
345              * can't be duplicate */
346             if (bind->keycode != current->keycode ||
347                 bind->mods != current->mods)
348                 continue;
349             context->has_errors = true;
350             if (current->keycode != 0) {
351                 ELOG("Duplicate keybinding in config file:\n  modmask %d with keycode %d, command \"%s\"\n",
352                      current->mods, current->keycode, current->command);
353             } else {
354                 ELOG("Duplicate keybinding in config file:\n  modmask %d with keysym %s, command \"%s\"\n",
355                      current->mods, current->symbol, current->command);
356             }
357         }
358     }
359 }
360
361 void parse_file(const char *f) {
362     SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
363     int fd, ret, read_bytes = 0;
364     struct stat stbuf;
365     char *buf;
366     FILE *fstr;
367     char buffer[1026], key[512], value[512];
368
369     if ((fd = open(f, O_RDONLY)) == -1)
370         die("Could not open configuration file: %s\n", strerror(errno));
371
372     if (fstat(fd, &stbuf) == -1)
373         die("Could not fstat file: %s\n", strerror(errno));
374
375     buf = scalloc((stbuf.st_size + 1) * sizeof(char));
376     while (read_bytes < stbuf.st_size) {
377         if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
378             die("Could not read(): %s\n", strerror(errno));
379         read_bytes += ret;
380     }
381
382     if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
383         die("Could not lseek: %s\n", strerror(errno));
384
385     if ((fstr = fdopen(fd, "r")) == NULL)
386         die("Could not fdopen: %s\n", strerror(errno));
387
388     while (!feof(fstr)) {
389         if (fgets(buffer, 1024, fstr) == NULL) {
390             if (feof(fstr))
391                 break;
392             die("Could not read configuration file\n");
393         }
394
395         /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
396         if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
397             key[0] == '#' || strlen(key) < 3)
398             continue;
399
400         if (strcasecmp(key, "set") == 0) {
401             if (value[0] != '$') {
402                 ELOG("Malformed variable assignment, name has to start with $\n");
403                 continue;
404             }
405
406             /* get key/value for this variable */
407             char *v_key = value, *v_value;
408             if ((v_value = strstr(value, " ")) == NULL &&
409                 (v_value = strstr(value, "\t")) == NULL) {
410                 ELOG("Malformed variable assignment, need a value\n");
411                 continue;
412             }
413
414             *(v_value++) = '\0';
415
416             struct Variable *new = scalloc(sizeof(struct Variable));
417             new->key = sstrdup(v_key);
418             new->value = sstrdup(v_value);
419             SLIST_INSERT_HEAD(&variables, new, variables);
420             DLOG("Got new variable %s = %s\n", v_key, v_value);
421             continue;
422         }
423     }
424     fclose(fstr);
425
426     /* For every custom variable, see how often it occurs in the file and
427      * how much extra bytes it requires when replaced. */
428     struct Variable *current, *nearest;
429     int extra_bytes = 0;
430     /* We need to copy the buffer because we need to invalidate the
431      * variables (otherwise we will count them twice, which is bad when
432      * 'extra' is negative) */
433     char *bufcopy = sstrdup(buf);
434     SLIST_FOREACH(current, &variables, variables) {
435         int extra = (strlen(current->value) - strlen(current->key));
436         char *next;
437         for (next = bufcopy;
438              (next = strcasestr(bufcopy + (next - bufcopy), current->key)) != NULL;
439              next += strlen(current->key)) {
440             *next = '_';
441             extra_bytes += extra;
442         }
443     }
444     FREE(bufcopy);
445
446     /* Then, allocate a new buffer and copy the file over to the new one,
447      * but replace occurences of our variables */
448     char *walk = buf, *destwalk;
449     char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
450     destwalk = new;
451     while (walk < (buf + stbuf.st_size)) {
452         /* Find the next variable */
453         SLIST_FOREACH(current, &variables, variables)
454             current->next_match = strcasestr(walk, current->key);
455         nearest = NULL;
456         int distance = stbuf.st_size;
457         SLIST_FOREACH(current, &variables, variables) {
458             if (current->next_match == NULL)
459                 continue;
460             if ((current->next_match - walk) < distance) {
461                 distance = (current->next_match - walk);
462                 nearest = current;
463             }
464         }
465         if (nearest == NULL) {
466             /* If there are no more variables, we just copy the rest */
467             strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
468             destwalk += (buf + stbuf.st_size) - walk;
469             *destwalk = '\0';
470             break;
471         } else {
472             /* Copy until the next variable, then copy its value */
473             strncpy(destwalk, walk, distance);
474             strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
475             walk += distance + strlen(nearest->key);
476             destwalk += distance + strlen(nearest->value);
477         }
478     }
479
480     /* analyze the string to find out whether this is an old config file (3.x)
481      * or a new config file (4.x). If it’s old, we run the converter script. */
482     int version = detect_version(buf);
483     if (version == 3) {
484         /* We need to convert this v3 configuration */
485         char *converted = migrate_config(new, stbuf.st_size);
486         if (converted != NULL) {
487             printf("\n");
488             printf("****************************************************************\n");
489             printf("NOTE: Automatically converted configuration file from v3 to v4.\n");
490             printf("\n");
491             printf("Please convert your config file to v4. You can use this command:\n");
492             printf("    mv %s %s.O\n", f, f);
493             printf("    i3-migrate-config-to-v4 %s.O > %s\n", f, f);
494             printf("****************************************************************\n");
495             printf("\n");
496             free(new);
497             new = converted;
498         } else {
499             printf("\n");
500             printf("**********************************************************************\n");
501             printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
502             printf("was not correctly installed on your system?\n");
503             printf("**********************************************************************\n");
504             printf("\n");
505         }
506     }
507
508     /* now lex/parse it */
509     yy_scan_string(new);
510
511     context = scalloc(sizeof(struct context));
512     context->filename = f;
513
514     if (yyparse() != 0) {
515         fprintf(stderr, "Could not parse configfile\n");
516         exit(1);
517     }
518
519     check_for_duplicate_bindings(context);
520
521     if (context->has_errors) {
522         start_configerror_nagbar(f);
523     }
524
525     yylex_destroy();
526     FREE(context->line_copy);
527     free(context);
528     free(new);
529     free(buf);
530
531     while (!SLIST_EMPTY(&variables)) {
532         current = SLIST_FIRST(&variables);
533         FREE(current->key);
534         FREE(current->value);
535         SLIST_REMOVE_HEAD(&variables, variables);
536         FREE(current);
537     }
538 }
539
540 %}
541
542 %error-verbose
543 %lex-param { struct context *context }
544
545 %union {
546     int number;
547     char *string;
548     uint32_t *single_color;
549     struct Colortriple *color;
550     Match *match;
551     struct Binding *binding;
552 }
553
554 %token  <number>        NUMBER                      "<number>"
555 %token  <string>        WORD                        "<word>"
556 %token  <string>        STR                         "<string>"
557 %token  <string>        STR_NG                      "<string (non-greedy)>"
558 %token  <string>        HEX                         "<hex>"
559 %token  <string>        OUTPUT                      "<RandR output>"
560 %token                  TOKBINDCODE
561 %token                  TOKTERMINAL
562 %token                  TOKCOMMENT                  "<comment>"
563 %token                  TOKFONT                     "font"
564 %token                  TOKBINDSYM                  "bindsym"
565 %token  <number>        MODIFIER                    "<modifier>"
566 %token                  TOKCONTROL                  "control"
567 %token                  TOKSHIFT                    "shift"
568 %token                  TOKFLOATING_MODIFIER        "floating_modifier"
569 %token  <string>        QUOTEDSTRING                "<quoted string>"
570 %token                  TOKWORKSPACE                "workspace"
571 %token                  TOKOUTPUT                   "output"
572 %token                  TOKASSIGN                   "assign"
573 %token                  TOKSET
574 %token                  TOKIPCSOCKET                "ipc_socket"
575 %token                  TOKRESTARTSTATE             "restart_state"
576 %token                  TOKEXEC                     "exec"
577 %token                  TOKEXEC_ALWAYS              "exec_always"
578 %token  <single_color>  TOKSINGLECOLOR
579 %token  <color>         TOKCOLOR
580 %token                  TOKARROW                    "→"
581 %token                  TOKMODE                     "mode"
582 %token                  TOK_ORIENTATION             "default_orientation"
583 %token                  TOK_HORIZ                   "horizontal"
584 %token                  TOK_VERT                    "vertical"
585 %token                  TOK_AUTO                    "auto"
586 %token                  TOK_WORKSPACE_LAYOUT        "workspace_layout"
587 %token                  TOKNEWWINDOW                "new_window"
588 %token                  TOKNEWFLOAT                 "new_float"
589 %token                  TOK_NORMAL                  "normal"
590 %token                  TOK_NONE                    "none"
591 %token                  TOK_1PIXEL                  "1pixel"
592 %token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
593 %token                  TOK_FORCE_FOCUS_WRAPPING    "force_focus_wrapping"
594 %token                  TOKWORKSPACEBAR             "workspace_bar"
595 %token                  TOK_DEFAULT                 "default"
596 %token                  TOK_STACKING                "stacking"
597 %token                  TOK_TABBED                  "tabbed"
598 %token  <number>        TOKSTACKLIMIT               "stack-limit"
599 %token                  TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen"
600 %token                  TOK_IGNORE                  "ignore"
601 %token                  TOK_LEAVE_FULLSCREEN        "leave_fullscreen"
602 %token                  TOK_FOR_WINDOW              "for_window"
603
604 %token              TOK_MARK            "mark"
605 %token              TOK_CLASS           "class"
606 %token              TOK_INSTANCE        "instance"
607 %token              TOK_ID              "id"
608 %token              TOK_CON_ID          "con_id"
609 %token              TOK_TITLE           "title"
610
611 %type   <binding>       binding
612 %type   <binding>       bindcode
613 %type   <binding>       bindsym
614 %type   <number>        binding_modifiers
615 %type   <number>        binding_modifier
616 %type   <number>        direction
617 %type   <number>        layout_mode
618 %type   <number>        border_style
619 %type   <number>        new_window
620 %type   <number>        new_float
621 %type   <number>        colorpixel
622 %type   <number>        bool
623 %type   <number>        popup_setting
624 %type   <string>        command
625 %type   <string>        word_or_number
626 %type   <string>        optional_workspace_name
627 %type   <string>        workspace_name
628 %type   <string>        window_class
629
630 %%
631
632 lines: /* empty */
633     | lines error
634     | lines line
635     ;
636
637 line:
638     bindline
639     | for_window
640     | mode
641     | floating_modifier
642     | orientation
643     | workspace_layout
644     | new_window
645     | new_float
646     | focus_follows_mouse
647     | force_focus_wrapping
648     | workspace_bar
649     | workspace
650     | assign
651     | ipcsocket
652     | restart_state
653     | exec
654     | exec_always
655     | single_color
656     | color
657     | terminal
658     | font
659     | comment
660     | popup_during_fullscreen
661     ;
662
663 comment:
664     TOKCOMMENT
665     ;
666
667 command:
668     STR
669     ;
670
671 bindline:
672     binding
673     {
674         TAILQ_INSERT_TAIL(bindings, $1, bindings);
675     }
676     ;
677
678 binding:
679     TOKBINDCODE bindcode         { $$ = $2; }
680     | TOKBINDSYM bindsym         { $$ = $2; }
681     ;
682
683 bindcode:
684     binding_modifiers NUMBER command
685     {
686         printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $3);
687         Binding *new = scalloc(sizeof(Binding));
688
689         new->keycode = $2;
690         new->mods = $1;
691         new->command = $3;
692
693         $$ = new;
694     }
695     ;
696
697 bindsym:
698     binding_modifiers word_or_number command
699     {
700         printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $3);
701         Binding *new = scalloc(sizeof(Binding));
702
703         new->symbol = $2;
704         new->mods = $1;
705         new->command = $3;
706
707         $$ = new;
708     }
709     ;
710
711 for_window:
712     TOK_FOR_WINDOW match command
713     {
714         printf("\t should execute command %s for the criteria mentioned above\n", $3);
715         Assignment *assignment = scalloc(sizeof(Assignment));
716         assignment->type = A_COMMAND;
717         assignment->match = current_match;
718         assignment->dest.command = $3;
719         TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
720     }
721     ;
722
723 match:
724     | matchstart criteria matchend
725     {
726         printf("match parsed\n");
727     }
728     ;
729
730 matchstart:
731     '['
732     {
733         printf("start\n");
734         match_init(&current_match);
735     }
736     ;
737
738 matchend:
739     ']'
740     {
741         printf("match specification finished\n");
742     }
743     ;
744
745 criteria:
746     criteria criterion
747     | criterion
748     ;
749
750 criterion:
751     TOK_CLASS '=' STR
752     {
753         printf("criteria: class = %s\n", $3);
754         current_match.class = regex_new($3);
755         free($3);
756     }
757     | TOK_INSTANCE '=' STR
758     {
759         printf("criteria: instance = %s\n", $3);
760         current_match.instance = regex_new($3);
761         free($3);
762     }
763     | TOK_CON_ID '=' STR
764     {
765         printf("criteria: id = %s\n", $3);
766         char *end;
767         long parsed = strtol($3, &end, 10);
768         if (parsed == LONG_MIN ||
769             parsed == LONG_MAX ||
770             parsed < 0 ||
771             (end && *end != '\0')) {
772             ELOG("Could not parse con id \"%s\"\n", $3);
773         } else {
774             current_match.con_id = (Con*)parsed;
775             printf("id as int = %p\n", current_match.con_id);
776         }
777     }
778     | TOK_ID '=' STR
779     {
780         printf("criteria: window id = %s\n", $3);
781         char *end;
782         long parsed = strtol($3, &end, 10);
783         if (parsed == LONG_MIN ||
784             parsed == LONG_MAX ||
785             parsed < 0 ||
786             (end && *end != '\0')) {
787             ELOG("Could not parse window id \"%s\"\n", $3);
788         } else {
789             current_match.id = parsed;
790             printf("window id as int = %d\n", current_match.id);
791         }
792     }
793     | TOK_MARK '=' STR
794     {
795         printf("criteria: mark = %s\n", $3);
796         current_match.mark = regex_new($3);
797         free($3);
798     }
799     | TOK_TITLE '=' STR
800     {
801         printf("criteria: title = %s\n", $3);
802         current_match.title = regex_new($3);
803         free($3);
804     }
805     ;
806
807
808
809 word_or_number:
810     WORD
811     | NUMBER
812     {
813         asprintf(&$$, "%d", $1);
814     }
815     ;
816
817 mode:
818     TOKMODE QUOTEDSTRING '{' modelines '}'
819     {
820         if (strcasecmp($2, "default") == 0) {
821             printf("You cannot use the name \"default\" for your mode\n");
822             exit(1);
823         }
824         printf("\t now in mode %s\n", $2);
825         printf("\t current bindings = %p\n", current_bindings);
826         Binding *binding;
827         TAILQ_FOREACH(binding, current_bindings, bindings) {
828             printf("got binding on mods %d, keycode %d, symbol %s, command %s\n",
829                             binding->mods, binding->keycode, binding->symbol, binding->command);
830         }
831
832         struct Mode *mode = scalloc(sizeof(struct Mode));
833         mode->name = $2;
834         mode->bindings = current_bindings;
835         current_bindings = NULL;
836         SLIST_INSERT_HEAD(&modes, mode, modes);
837     }
838     ;
839
840
841 modelines:
842     /* empty */
843     | modelines modeline
844     ;
845
846 modeline:
847     comment
848     | binding
849     {
850         if (current_bindings == NULL) {
851             current_bindings = scalloc(sizeof(struct bindings_head));
852             TAILQ_INIT(current_bindings);
853         }
854
855         TAILQ_INSERT_TAIL(current_bindings, $1, bindings);
856     }
857     ;
858
859 floating_modifier:
860     TOKFLOATING_MODIFIER binding_modifiers
861     {
862         DLOG("floating modifier = %d\n", $2);
863         config.floating_modifier = $2;
864     }
865     ;
866
867 orientation:
868     TOK_ORIENTATION direction
869     {
870         DLOG("New containers should start with split direction %d\n", $2);
871         config.default_orientation = $2;
872     }
873     ;
874
875 direction:
876     TOK_HORIZ       { $$ = HORIZ; }
877     | TOK_VERT      { $$ = VERT; }
878     | TOK_AUTO      { $$ = NO_ORIENTATION; }
879     ;
880
881 workspace_layout:
882     TOK_WORKSPACE_LAYOUT layout_mode
883     {
884         DLOG("new containers will be in mode %d\n", $2);
885         config.default_layout = $2;
886
887 #if 0
888         /* We also need to change the layout of the already existing
889          * workspaces here. Workspaces may exist at this point because
890          * of the other directives which are modifying workspaces
891          * (setting the preferred screen or name). While the workspace
892          * objects are already created, they have never been used.
893          * Thus, the user very likely awaits the default container mode
894          * to trigger in this case, regardless of where it is inside
895          * his configuration file. */
896         Workspace *ws;
897         TAILQ_FOREACH(ws, workspaces, workspaces) {
898                 if (ws->table == NULL)
899                         continue;
900                 switch_layout_mode(global_conn,
901                                    ws->table[0][0],
902                                    config.container_mode);
903         }
904 #endif
905     }
906     | TOK_WORKSPACE_LAYOUT TOKSTACKLIMIT TOKSTACKLIMIT NUMBER
907     {
908         DLOG("stack-limit %d with val %d\n", $3, $4);
909         config.container_stack_limit = $3;
910         config.container_stack_limit_value = $4;
911
912 #if 0
913         /* See the comment above */
914         Workspace *ws;
915         TAILQ_FOREACH(ws, workspaces, workspaces) {
916                 if (ws->table == NULL)
917                         continue;
918                 Container *con = ws->table[0][0];
919                 con->stack_limit = config.container_stack_limit;
920                 con->stack_limit_value = config.container_stack_limit_value;
921         }
922 #endif
923     }
924     ;
925
926 layout_mode:
927     TOK_DEFAULT       { $$ = L_DEFAULT; }
928     | TOK_STACKING    { $$ = L_STACKED; }
929     | TOK_TABBED      { $$ = L_TABBED; }
930     ;
931
932 new_window:
933     TOKNEWWINDOW border_style
934     {
935         DLOG("new windows should start with border style %d\n", $2);
936         config.default_border = $2;
937     }
938     ;
939
940 new_float:
941     TOKNEWFLOAT border_style
942     {
943        DLOG("new floating windows should start with border style %d\n", $2);
944        config.default_floating_border = $2;
945     }
946     ;
947
948 border_style:
949     TOK_NORMAL      { $$ = BS_NORMAL; }
950     | TOK_NONE      { $$ = BS_NONE; }
951     | TOK_1PIXEL    { $$ = BS_1PIXEL; }
952     ;
953
954 bool:
955     NUMBER
956     {
957         $$ = ($1 == 1);
958     }
959     | WORD
960     {
961         DLOG("checking word \"%s\"\n", $1);
962         $$ = (strcasecmp($1, "yes") == 0 ||
963               strcasecmp($1, "true") == 0 ||
964               strcasecmp($1, "on") == 0 ||
965               strcasecmp($1, "enable") == 0 ||
966               strcasecmp($1, "active") == 0);
967     }
968     ;
969
970 focus_follows_mouse:
971     TOKFOCUSFOLLOWSMOUSE bool
972     {
973         DLOG("focus follows mouse = %d\n", $2);
974         config.disable_focus_follows_mouse = !($2);
975     }
976     ;
977
978 force_focus_wrapping:
979     TOK_FORCE_FOCUS_WRAPPING bool
980     {
981         DLOG("force focus wrapping = %d\n", $2);
982         config.force_focus_wrapping = $2;
983     }
984     ;
985
986 workspace_bar:
987     TOKWORKSPACEBAR bool
988     {
989         DLOG("workspace bar = %d\n", $2);
990         config.disable_workspace_bar = !($2);
991     }
992     ;
993
994 workspace:
995     TOKWORKSPACE NUMBER TOKOUTPUT OUTPUT optional_workspace_name
996     {
997         int ws_num = $2;
998         if (ws_num < 1) {
999             DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
1000         } else {
1001             char *ws_name = NULL;
1002             if ($5 == NULL) {
1003                 asprintf(&ws_name, "%d", ws_num);
1004             } else {
1005                 ws_name = $5;
1006             }
1007
1008             DLOG("Should assign workspace %s to output %s\n", ws_name, $4);
1009             /* Check for earlier assignments of the same workspace so that we
1010              * don’t have assignments of a single workspace to different
1011              * outputs */
1012             struct Workspace_Assignment *assignment;
1013             bool duplicate = false;
1014             TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
1015                 if (strcasecmp(assignment->name, ws_name) == 0) {
1016                     ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
1017                          ws_name);
1018                     assignment->output = $4;
1019                     duplicate = true;
1020                 }
1021             }
1022             if (!duplicate) {
1023                 assignment = scalloc(sizeof(struct Workspace_Assignment));
1024                 assignment->name = ws_name;
1025                 assignment->output = $4;
1026                 TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
1027             }
1028         }
1029     }
1030     | TOKWORKSPACE NUMBER workspace_name
1031     {
1032         int ws_num = $2;
1033         if (ws_num < 1) {
1034             DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
1035         } else {
1036             DLOG("workspace name to: %s\n", $3);
1037 #if 0
1038             if ($<string>3 != NULL) {
1039                     workspace_set_name(workspace_get(ws_num - 1), $<string>3);
1040                     free($<string>3);
1041             }
1042 #endif
1043         }
1044     }
1045     ;
1046
1047 optional_workspace_name:
1048     /* empty */          { $$ = NULL; }
1049     | workspace_name     { $$ = $1; }
1050     ;
1051
1052 workspace_name:
1053     QUOTEDSTRING         { $$ = $1; }
1054     | STR                { $$ = $1; }
1055     | WORD               { $$ = $1; }
1056     ;
1057
1058 assign:
1059     TOKASSIGN window_class STR
1060     {
1061         /* TODO: the assign command also needs some kind of new syntax where we
1062          * just use criteria. Then deprecate the old form */
1063         printf("assignment of %s to *%s*\n", $2, $3);
1064         char *workspace = $3;
1065         char *criteria = $2;
1066
1067         Assignment *assignment = scalloc(sizeof(Assignment));
1068         Match *match = &(assignment->match);
1069         match_init(match);
1070
1071         char *separator = NULL;
1072         if ((separator = strchr(criteria, '/')) != NULL) {
1073             *(separator++) = '\0';
1074             match->title = regex_new(separator);
1075             printf("  title = %s\n", separator);
1076         }
1077         if (*criteria != '\0') {
1078             match->class = regex_new(criteria);
1079             printf("  class = %s\n", criteria);
1080         }
1081         free(criteria);
1082
1083         /* Compatibility with older versions: If the assignment target starts
1084          * with ~, we create the equivalent of:
1085          *
1086          * for_window [class="foo"] floating enable
1087          */
1088         if (*workspace == '~') {
1089             workspace++;
1090             if (*workspace == '\0') {
1091                 /* This assignment was *only* for floating */
1092                 assignment->type = A_COMMAND;
1093                 assignment->dest.command = sstrdup("floating enable");
1094                 TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
1095                 break;
1096             } else {
1097                 /* Create a new assignment and continue afterwards */
1098                 Assignment *floating = scalloc(sizeof(Assignment));
1099                 match_copy(&(floating->match), match);
1100                 floating->type = A_COMMAND;
1101                 floating->dest.command = sstrdup("floating enable");
1102                 TAILQ_INSERT_TAIL(&assignments, floating, assignments);
1103             }
1104         }
1105
1106         assignment->type = A_TO_WORKSPACE;
1107         assignment->dest.workspace = workspace;
1108         TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
1109     }
1110     ;
1111
1112 window_class:
1113     QUOTEDSTRING
1114     | STR_NG
1115     ;
1116
1117 ipcsocket:
1118     TOKIPCSOCKET STR
1119     {
1120         config.ipc_socket_path = $2;
1121     }
1122     ;
1123
1124 restart_state:
1125     TOKRESTARTSTATE STR
1126     {
1127         config.restart_state_path = $2;
1128     }
1129     ;
1130
1131 exec:
1132     TOKEXEC STR
1133     {
1134         struct Autostart *new = smalloc(sizeof(struct Autostart));
1135         new->command = $2;
1136         TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
1137     }
1138     ;
1139
1140 exec_always:
1141     TOKEXEC_ALWAYS STR
1142     {
1143         struct Autostart *new = smalloc(sizeof(struct Autostart));
1144         new->command = $2;
1145         TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
1146     }
1147     ;
1148
1149 terminal:
1150     TOKTERMINAL STR
1151     {
1152         ELOG("The terminal option is DEPRECATED and has no effect. "
1153             "Please remove it from your configuration file.\n");
1154     }
1155     ;
1156
1157 font:
1158     TOKFONT STR
1159     {
1160         config.font = load_font($2, true);
1161         printf("font %s\n", $2);
1162         free($2);
1163     }
1164     ;
1165
1166 single_color:
1167     TOKSINGLECOLOR colorpixel
1168     {
1169         uint32_t *dest = $1;
1170         *dest = $2;
1171     }
1172     ;
1173
1174 color:
1175     TOKCOLOR colorpixel colorpixel colorpixel
1176     {
1177         struct Colortriple *dest = $1;
1178
1179         dest->border = $2;
1180         dest->background = $3;
1181         dest->text = $4;
1182     }
1183     ;
1184
1185 colorpixel:
1186     '#' HEX
1187     {
1188         char *hex;
1189         if (asprintf(&hex, "#%s", $2) == -1)
1190             die("asprintf()");
1191         free($2);
1192         $$ = get_colorpixel(hex);
1193         free(hex);
1194     }
1195     ;
1196
1197
1198 binding_modifiers:
1199     /* NULL */                               { $$ = 0; }
1200     | binding_modifier
1201     | binding_modifiers '+' binding_modifier { $$ = $1 | $3; }
1202     | binding_modifiers '+'                  { $$ = $1; }
1203     ;
1204
1205 binding_modifier:
1206     MODIFIER        { $$ = $1; }
1207     | TOKCONTROL    { $$ = BIND_CONTROL; }
1208     | TOKSHIFT      { $$ = BIND_SHIFT; }
1209     ;
1210
1211 popup_during_fullscreen:
1212     TOK_POPUP_DURING_FULLSCREEN popup_setting
1213     {
1214         DLOG("popup_during_fullscreen setting: %d\n", $2);
1215         config.popup_during_fullscreen = $2;
1216     }
1217     ;
1218
1219 popup_setting:
1220     TOK_IGNORE              { $$ = PDF_IGNORE; }
1221     | TOK_LEAVE_FULLSCREEN  { $$ = PDF_LEAVE_FULLSCREEN; }
1222     ;