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