]> git.sur5r.net Git - i3/i3/blob - src/cfgparse.y
cfgparse: Don’t use asprintf when parsing hex colors
[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 static Barconfig current_bar;
18
19 typedef struct yy_buffer_state *YY_BUFFER_STATE;
20 extern int yylex(struct context *context);
21 extern int yyparse(void);
22 extern int yylex_destroy(void);
23 extern FILE *yyin;
24 YY_BUFFER_STATE yy_scan_string(const char *);
25
26 static struct bindings_head *current_bindings;
27 static struct context *context;
28
29 /* We don’t need yydebug for now, as we got decent error messages using
30  * yyerror(). Should you ever want to extend the parser, it might be handy
31  * to just comment it in again, so it stays here. */
32 //int yydebug = 1;
33
34 void yyerror(const char *error_message) {
35     context->has_errors = true;
36
37     ELOG("\n");
38     ELOG("CONFIG: %s\n", error_message);
39     ELOG("CONFIG: in file \"%s\", line %d:\n",
40         context->filename, context->line_number);
41     ELOG("CONFIG:   %s\n", context->line_copy);
42     char buffer[context->last_column+1];
43     buffer[context->last_column] = '\0';
44     for (int c = 1; c <= context->last_column; c++)
45         buffer[c-1] = (c >= context->first_column ? '^' : ' ');
46     ELOG("CONFIG:   %s\n", buffer);
47     ELOG("\n");
48 }
49
50 int yywrap() {
51     return 1;
52 }
53
54 /*
55  * Goes through each line of buf (separated by \n) and checks for statements /
56  * commands which only occur in i3 v4 configuration files. If it finds any, it
57  * returns version 4, otherwise it returns version 3.
58  *
59  */
60 static int detect_version(char *buf) {
61     char *walk = buf;
62     char *line = buf;
63     while (*walk != '\0') {
64         if (*walk != '\n') {
65             walk++;
66             continue;
67         }
68
69         /* check for some v4-only statements */
70         if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
71             strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
72             strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
73             strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
74             printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
75             return 4;
76         }
77
78         /* if this is a bind statement, we can check the command */
79         if (strncasecmp(line, "bind", strlen("bind")) == 0) {
80             char *bind = strchr(line, ' ');
81             if (bind == NULL)
82                 goto next;
83             while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
84                 bind++;
85             if (*bind == '\0')
86                 goto next;
87             if ((bind = strchr(bind, ' ')) == NULL)
88                 goto next;
89             while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
90                 bind++;
91             if (*bind == '\0')
92                 goto next;
93             if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
94                 strncasecmp(bind, "floating", strlen("floating")) == 0 ||
95                 strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
96                 strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
97                 strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
98                 strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
99                 strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
100                 strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
101                 strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
102                 strncasecmp(bind, "border borderless", strlen("border borderless")) == 0) {
103                 printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
104                 return 4;
105             }
106         }
107
108 next:
109         /* advance to the next line */
110         walk++;
111         line = walk;
112     }
113
114     return 3;
115 }
116
117 /*
118  * Calls i3-migrate-config-to-v4 to migrate a configuration file (input
119  * buffer).
120  *
121  * Returns the converted config file or NULL if there was an error (for
122  * example the script could not be found in $PATH or the i3 executable’s
123  * directory).
124  *
125  */
126 static char *migrate_config(char *input, off_t size) {
127     int writepipe[2];
128     int readpipe[2];
129
130     if (pipe(writepipe) != 0 ||
131         pipe(readpipe) != 0) {
132         warn("migrate_config: Could not create pipes");
133         return NULL;
134     }
135
136     pid_t pid = fork();
137     if (pid == -1) {
138         warn("Could not fork()");
139         return NULL;
140     }
141
142     /* child */
143     if (pid == 0) {
144         /* close writing end of writepipe, connect reading side to stdin */
145         close(writepipe[1]);
146         dup2(writepipe[0], 0);
147
148         /* close reading end of readpipe, connect writing side to stdout */
149         close(readpipe[0]);
150         dup2(readpipe[1], 1);
151
152         static char *argv[] = {
153             NULL, /* will be replaced by the executable path */
154             NULL
155         };
156         exec_i3_utility("i3-migrate-config-to-v4", argv);
157     }
158
159     /* parent */
160
161     /* close reading end of the writepipe (connected to the script’s stdin) */
162     close(writepipe[0]);
163
164     /* write the whole config file to the pipe, the script will read everything
165      * immediately */
166     int written = 0;
167     int ret;
168     while (written < size) {
169         if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
170             warn("Could not write to pipe");
171             return NULL;
172         }
173         written += ret;
174     }
175     close(writepipe[1]);
176
177     /* close writing end of the readpipe (connected to the script’s stdout) */
178     close(readpipe[1]);
179
180     /* read the script’s output */
181     int conv_size = 65535;
182     char *converted = malloc(conv_size);
183     int read_bytes = 0;
184     do {
185         if (read_bytes == conv_size) {
186             conv_size += 65535;
187             converted = realloc(converted, conv_size);
188         }
189         ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
190         if (ret == -1) {
191             warn("Cannot read from pipe");
192             FREE(converted);
193             return NULL;
194         }
195         read_bytes += ret;
196     } while (ret > 0);
197
198     /* get the returncode */
199     int status;
200     wait(&status);
201     if (!WIFEXITED(status)) {
202         fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
203         return NULL;
204     }
205
206     int returncode = WEXITSTATUS(status);
207     if (returncode != 0) {
208         fprintf(stderr, "Migration process exit code was != 0\n");
209         if (returncode == 2) {
210             fprintf(stderr, "could not start the migration script\n");
211             /* TODO: script was not found. tell the user to fix his system or create a v4 config */
212         } else if (returncode == 1) {
213             fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
214             fprintf(stderr, "# i3 config file (v4)\n");
215             /* TODO: nag the user with a message to include a hint for i3 in his config file */
216         }
217         return NULL;
218     }
219
220     return converted;
221 }
222
223 /*
224  * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
225  * it exited (or could not be started, depending on the exit code).
226  *
227  */
228 static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
229     ev_child_stop(EV_A_ watcher);
230     if (!WIFEXITED(watcher->rstatus)) {
231         fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
232         return;
233     }
234
235     int exitcode = WEXITSTATUS(watcher->rstatus);
236     printf("i3-nagbar process exited with status %d\n", exitcode);
237     if (exitcode == 2) {
238         fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
239     }
240
241     configerror_pid = -1;
242 }
243
244 /* We need ev >= 4 for the following code. Since it is not *that* important (it
245  * only makes sure that there are no i3-nagbar instances left behind) we still
246  * support old systems with libev 3. */
247 #if EV_VERSION_MAJOR >= 4
248 /*
249  * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
250  * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
251  *
252  */
253 static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
254     if (configerror_pid != -1) {
255         LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid);
256         kill(configerror_pid, SIGKILL);
257     }
258 }
259 #endif
260
261 /*
262  * Starts an i3-nagbar process which alerts the user that his configuration
263  * file contains one or more errors. Also offers two buttons: One to launch an
264  * $EDITOR on the config file and another one to launch a $PAGER on the error
265  * logfile.
266  *
267  */
268 static void start_configerror_nagbar(const char *config_path) {
269     if (only_check_config)
270         return;
271
272     fprintf(stderr, "Starting i3-nagbar due to configuration errors\n");
273     configerror_pid = fork();
274     if (configerror_pid == -1) {
275         warn("Could not fork()");
276         return;
277     }
278
279     /* child */
280     if (configerror_pid == 0) {
281         char *editaction,
282              *pageraction;
283         if (asprintf(&editaction, "i3-sensible-terminal -e sh -c \"i3-sensible-editor \\\"%s\\\" && i3-msg reload\"", config_path) == -1)
284             exit(1);
285         if (asprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename) == -1)
286             exit(1);
287         char *argv[] = {
288             NULL, /* will be replaced by the executable path */
289             "-m",
290             "You have an error in your i3 config file!",
291             "-b",
292             "edit config",
293             editaction,
294             (errorfilename ? "-b" : NULL),
295             "show errors",
296             pageraction,
297             NULL
298         };
299         exec_i3_utility("i3-nagbar", argv);
300     }
301
302     /* parent */
303     /* install a child watcher */
304     ev_child *child = smalloc(sizeof(ev_child));
305     ev_child_init(child, &nagbar_exited, configerror_pid, 0);
306     ev_child_start(main_loop, child);
307
308 /* We need ev >= 4 for the following code. Since it is not *that* important (it
309  * only makes sure that there are no i3-nagbar instances left behind) we still
310  * support old systems with libev 3. */
311 #if EV_VERSION_MAJOR >= 4
312     /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
313      * still running) */
314     ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
315     ev_cleanup_init(cleanup, nagbar_cleanup);
316     ev_cleanup_start(main_loop, cleanup);
317 #endif
318 }
319
320 /*
321  * Kills the configerror i3-nagbar process, if any.
322  *
323  * Called when reloading/restarting.
324  *
325  * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
326  * ev is assumed to handle it (reloading).
327  *
328  */
329 void kill_configerror_nagbar(bool wait_for_it) {
330     if (configerror_pid == -1)
331         return;
332
333     if (kill(configerror_pid, SIGTERM) == -1)
334         warn("kill(configerror_nagbar) failed");
335
336     if (!wait_for_it)
337         return;
338
339     /* When restarting, we don’t enter the ev main loop anymore and after the
340      * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
341      * for us and we would end up with a <defunct> process. Therefore we
342      * waitpid() here. */
343     waitpid(configerror_pid, NULL, 0);
344 }
345
346 /*
347  * Checks for duplicate key bindings (the same keycode or keysym is configured
348  * more than once). If a duplicate binding is found, a message is printed to
349  * stderr and the has_errors variable is set to true, which will start
350  * i3-nagbar.
351  *
352  */
353 static void check_for_duplicate_bindings(struct context *context) {
354     Binding *bind, *current;
355     TAILQ_FOREACH(current, bindings, bindings) {
356         TAILQ_FOREACH(bind, bindings, bindings) {
357             /* Abort when we reach the current keybinding, only check the
358              * bindings before */
359             if (bind == current)
360                 break;
361
362             /* Check if one is using keysym while the other is using bindsym.
363              * If so, skip. */
364             /* XXX: It should be checked at a later place (when translating the
365              * keysym to keycodes) if there are any duplicates */
366             if ((bind->symbol == NULL && current->symbol != NULL) ||
367                 (bind->symbol != NULL && current->symbol == NULL))
368                 continue;
369
370             /* If bind is NULL, current has to be NULL, too (see above).
371              * If the keycodes differ, it can't be a duplicate. */
372             if (bind->symbol != NULL &&
373                 strcasecmp(bind->symbol, current->symbol) != 0)
374                 continue;
375
376             /* Check if the keycodes or modifiers are different. If so, they
377              * can't be duplicate */
378             if (bind->keycode != current->keycode ||
379                 bind->mods != current->mods)
380                 continue;
381             context->has_errors = true;
382             if (current->keycode != 0) {
383                 ELOG("Duplicate keybinding in config file:\n  modmask %d with keycode %d, command \"%s\"\n",
384                      current->mods, current->keycode, current->command);
385             } else {
386                 ELOG("Duplicate keybinding in config file:\n  modmask %d with keysym %s, command \"%s\"\n",
387                      current->mods, current->symbol, current->command);
388             }
389         }
390     }
391 }
392
393 void parse_file(const char *f) {
394     SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
395     int fd, ret, read_bytes = 0;
396     struct stat stbuf;
397     char *buf;
398     FILE *fstr;
399     char buffer[1026], key[512], value[512];
400
401     if ((fd = open(f, O_RDONLY)) == -1)
402         die("Could not open configuration file: %s\n", strerror(errno));
403
404     if (fstat(fd, &stbuf) == -1)
405         die("Could not fstat file: %s\n", strerror(errno));
406
407     buf = scalloc((stbuf.st_size + 1) * sizeof(char));
408     while (read_bytes < stbuf.st_size) {
409         if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
410             die("Could not read(): %s\n", strerror(errno));
411         read_bytes += ret;
412     }
413
414     if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
415         die("Could not lseek: %s\n", strerror(errno));
416
417     if ((fstr = fdopen(fd, "r")) == NULL)
418         die("Could not fdopen: %s\n", strerror(errno));
419
420     while (!feof(fstr)) {
421         if (fgets(buffer, 1024, fstr) == NULL) {
422             if (feof(fstr))
423                 break;
424             die("Could not read configuration file\n");
425         }
426
427         /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
428         if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
429             key[0] == '#' || strlen(key) < 3)
430             continue;
431
432         if (strcasecmp(key, "set") == 0) {
433             if (value[0] != '$') {
434                 ELOG("Malformed variable assignment, name has to start with $\n");
435                 continue;
436             }
437
438             /* get key/value for this variable */
439             char *v_key = value, *v_value;
440             if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) {
441                 ELOG("Malformed variable assignment, need a value\n");
442                 continue;
443             }
444
445             if (!(v_value = strstr(value, " ")))
446                 v_value = strstr(value, "\t");
447
448             *(v_value++) = '\0';
449
450             struct Variable *new = scalloc(sizeof(struct Variable));
451             new->key = sstrdup(v_key);
452             new->value = sstrdup(v_value);
453             SLIST_INSERT_HEAD(&variables, new, variables);
454             DLOG("Got new variable %s = %s\n", v_key, v_value);
455             continue;
456         }
457     }
458     fclose(fstr);
459
460     /* For every custom variable, see how often it occurs in the file and
461      * how much extra bytes it requires when replaced. */
462     struct Variable *current, *nearest;
463     int extra_bytes = 0;
464     /* We need to copy the buffer because we need to invalidate the
465      * variables (otherwise we will count them twice, which is bad when
466      * 'extra' is negative) */
467     char *bufcopy = sstrdup(buf);
468     SLIST_FOREACH(current, &variables, variables) {
469         int extra = (strlen(current->value) - strlen(current->key));
470         char *next;
471         for (next = bufcopy;
472              next < (bufcopy + stbuf.st_size) &&
473              (next = strcasestr(next, current->key)) != NULL;
474              next += strlen(current->key)) {
475             *next = '_';
476             extra_bytes += extra;
477         }
478     }
479     FREE(bufcopy);
480
481     /* Then, allocate a new buffer and copy the file over to the new one,
482      * but replace occurences of our variables */
483     char *walk = buf, *destwalk;
484     char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
485     destwalk = new;
486     while (walk < (buf + stbuf.st_size)) {
487         /* Find the next variable */
488         SLIST_FOREACH(current, &variables, variables)
489             current->next_match = strcasestr(walk, current->key);
490         nearest = NULL;
491         int distance = stbuf.st_size;
492         SLIST_FOREACH(current, &variables, variables) {
493             if (current->next_match == NULL)
494                 continue;
495             if ((current->next_match - walk) < distance) {
496                 distance = (current->next_match - walk);
497                 nearest = current;
498             }
499         }
500         if (nearest == NULL) {
501             /* If there are no more variables, we just copy the rest */
502             strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
503             destwalk += (buf + stbuf.st_size) - walk;
504             *destwalk = '\0';
505             break;
506         } else {
507             /* Copy until the next variable, then copy its value */
508             strncpy(destwalk, walk, distance);
509             strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
510             walk += distance + strlen(nearest->key);
511             destwalk += distance + strlen(nearest->value);
512         }
513     }
514
515     /* analyze the string to find out whether this is an old config file (3.x)
516      * or a new config file (4.x). If it’s old, we run the converter script. */
517     int version = detect_version(buf);
518     if (version == 3) {
519         /* We need to convert this v3 configuration */
520         char *converted = migrate_config(new, stbuf.st_size);
521         if (converted != NULL) {
522             printf("\n");
523             printf("****************************************************************\n");
524             printf("NOTE: Automatically converted configuration file from v3 to v4.\n");
525             printf("\n");
526             printf("Please convert your config file to v4. You can use this command:\n");
527             printf("    mv %s %s.O\n", f, f);
528             printf("    i3-migrate-config-to-v4 %s.O > %s\n", f, f);
529             printf("****************************************************************\n");
530             printf("\n");
531             free(new);
532             new = converted;
533         } else {
534             printf("\n");
535             printf("**********************************************************************\n");
536             printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
537             printf("was not correctly installed on your system?\n");
538             printf("**********************************************************************\n");
539             printf("\n");
540         }
541     }
542
543     /* now lex/parse it */
544     yy_scan_string(new);
545
546     context = scalloc(sizeof(struct context));
547     context->filename = f;
548
549     if (yyparse() != 0) {
550         fprintf(stderr, "Could not parse configfile\n");
551         exit(1);
552     }
553
554     check_for_duplicate_bindings(context);
555
556     if (context->has_errors) {
557         start_configerror_nagbar(f);
558     }
559
560     yylex_destroy();
561     FREE(context->line_copy);
562     free(context);
563     free(new);
564     free(buf);
565
566     while (!SLIST_EMPTY(&variables)) {
567         current = SLIST_FIRST(&variables);
568         FREE(current->key);
569         FREE(current->value);
570         SLIST_REMOVE_HEAD(&variables, variables);
571         FREE(current);
572     }
573 }
574
575 %}
576
577 %error-verbose
578 %lex-param { struct context *context }
579
580 %union {
581     int number;
582     char *string;
583     uint32_t *single_color;
584     struct Colortriple *color;
585     Match *match;
586     struct Binding *binding;
587 }
588
589 %token  <number>        NUMBER                      "<number>"
590 %token  <string>        WORD                        "<word>"
591 %token  <string>        STR                         "<string>"
592 %token  <string>        STR_NG                      "<string (non-greedy)>"
593 %token  <string>        HEXCOLOR                    "#<hex>"
594 %token  <string>        OUTPUT                      "<RandR output>"
595 %token                  TOKBINDCODE
596 %token                  TOKTERMINAL
597 %token                  TOKCOMMENT                  "<comment>"
598 %token                  TOKFONT                     "font"
599 %token                  TOKBINDSYM                  "bindsym"
600 %token  <number>        MODIFIER                    "<modifier>"
601 %token                  TOKCONTROL                  "control"
602 %token                  TOKSHIFT                    "shift"
603 %token                  TOKFLOATING_MODIFIER        "floating_modifier"
604 %token  <string>        QUOTEDSTRING                "<quoted string>"
605 %token                  TOKWORKSPACE                "workspace"
606 %token                  TOKOUTPUT                   "output"
607 %token                  TOKASSIGN                   "assign"
608 %token                  TOKSET
609 %token                  TOKIPCSOCKET                "ipc_socket"
610 %token                  TOKRESTARTSTATE             "restart_state"
611 %token                  TOKEXEC                     "exec"
612 %token                  TOKEXEC_ALWAYS              "exec_always"
613 %token  <single_color>  TOKSINGLECOLOR
614 %token  <color>         TOKCOLOR
615 %token                  TOKARROW                    "→"
616 %token                  TOKMODE                     "mode"
617 %token                  TOK_BAR                     "bar"
618 %token                  TOK_ORIENTATION             "default_orientation"
619 %token                  TOK_HORIZ                   "horizontal"
620 %token                  TOK_VERT                    "vertical"
621 %token                  TOK_AUTO                    "auto"
622 %token                  TOK_WORKSPACE_LAYOUT        "workspace_layout"
623 %token                  TOKNEWWINDOW                "new_window"
624 %token                  TOKNEWFLOAT                 "new_float"
625 %token                  TOK_NORMAL                  "normal"
626 %token                  TOK_NONE                    "none"
627 %token                  TOK_1PIXEL                  "1pixel"
628 %token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
629 %token                  TOK_FORCE_FOCUS_WRAPPING    "force_focus_wrapping"
630 %token                  TOK_FORCE_XINERAMA          "force_xinerama"
631 %token                  TOK_WORKSPACE_AUTO_BAF      "workspace_auto_back_and_forth"
632 %token                  TOKWORKSPACEBAR             "workspace_bar"
633 %token                  TOK_DEFAULT                 "default"
634 %token                  TOK_STACKING                "stacking"
635 %token                  TOK_TABBED                  "tabbed"
636 %token  <number>        TOKSTACKLIMIT               "stack-limit"
637 %token                  TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen"
638 %token                  TOK_IGNORE                  "ignore"
639 %token                  TOK_LEAVE_FULLSCREEN        "leave_fullscreen"
640 %token                  TOK_FOR_WINDOW              "for_window"
641
642 %token                  TOK_BAR_OUTPUT              "output (bar)"
643 %token                  TOK_BAR_TRAY_OUTPUT         "tray_output"
644 %token                  TOK_BAR_SOCKET_PATH         "socket_path"
645 %token                  TOK_BAR_MODE                "mode"
646 %token                  TOK_BAR_HIDE                "hide"
647 %token                  TOK_BAR_DOCK                "dock"
648 %token                  TOK_BAR_POSITION            "position"
649 %token                  TOK_BAR_BOTTOM              "bottom"
650 %token                  TOK_BAR_TOP                 "top"
651 %token                  TOK_BAR_STATUS_COMMAND      "status_command"
652 %token                  TOK_BAR_FONT                "font"
653 %token                  TOK_BAR_WORKSPACE_BUTTONS   "workspace_buttons"
654 %token                  TOK_BAR_VERBOSE             "verbose"
655 %token                  TOK_BAR_COLORS              "colors"
656 %token                  TOK_BAR_COLOR_BACKGROUND    "background"
657 %token                  TOK_BAR_COLOR_STATUSLINE    "statusline"
658 %token                  TOK_BAR_COLOR_FOCUSED_WORKSPACE "focused_workspace"
659 %token                  TOK_BAR_COLOR_ACTIVE_WORKSPACE "active_workspace"
660 %token                  TOK_BAR_COLOR_INACTIVE_WORKSPACE "inactive_workspace"
661 %token                  TOK_BAR_COLOR_URGENT_WORKSPACE "urgent_workspace"
662
663 %token              TOK_MARK            "mark"
664 %token              TOK_CLASS           "class"
665 %token              TOK_INSTANCE        "instance"
666 %token              TOK_WINDOW_ROLE     "window_role"
667 %token              TOK_ID              "id"
668 %token              TOK_CON_ID          "con_id"
669 %token              TOK_TITLE           "title"
670
671 %type   <binding>       binding
672 %type   <binding>       bindcode
673 %type   <binding>       bindsym
674 %type   <number>        binding_modifiers
675 %type   <number>        binding_modifier
676 %type   <number>        direction
677 %type   <number>        layout_mode
678 %type   <number>        border_style
679 %type   <number>        new_window
680 %type   <number>        new_float
681 %type   <number>        colorpixel
682 %type   <number>        bool
683 %type   <number>        popup_setting
684 %type   <number>        bar_position_position
685 %type   <number>        bar_mode_mode
686 %type   <string>        command
687 %type   <string>        word_or_number
688 %type   <string>        optional_workspace_name
689 %type   <string>        workspace_name
690 %type   <string>        window_class
691
692 %%
693
694 lines: /* empty */
695     | lines error
696     | lines line
697     ;
698
699 line:
700     bindline
701     | for_window
702     | mode
703     | bar
704     | floating_modifier
705     | orientation
706     | workspace_layout
707     | new_window
708     | new_float
709     | focus_follows_mouse
710     | force_focus_wrapping
711     | force_xinerama
712     | workspace_back_and_forth
713     | workspace_bar
714     | workspace
715     | assign
716     | ipcsocket
717     | restart_state
718     | exec
719     | exec_always
720     | single_color
721     | color
722     | terminal
723     | font
724     | comment
725     | popup_during_fullscreen
726     ;
727
728 comment:
729     TOKCOMMENT
730     ;
731
732 command:
733     STR
734     ;
735
736 bindline:
737     binding
738     {
739         TAILQ_INSERT_TAIL(bindings, $1, bindings);
740     }
741     ;
742
743 binding:
744     TOKBINDCODE bindcode         { $$ = $2; }
745     | TOKBINDSYM bindsym         { $$ = $2; }
746     ;
747
748 bindcode:
749     binding_modifiers NUMBER command
750     {
751         printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $3);
752         Binding *new = scalloc(sizeof(Binding));
753
754         new->keycode = $2;
755         new->mods = $1;
756         new->command = $3;
757
758         $$ = new;
759     }
760     ;
761
762 bindsym:
763     binding_modifiers word_or_number command
764     {
765         printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $3);
766         Binding *new = scalloc(sizeof(Binding));
767
768         new->symbol = $2;
769         new->mods = $1;
770         new->command = $3;
771
772         $$ = new;
773     }
774     ;
775
776 for_window:
777     TOK_FOR_WINDOW match command
778     {
779         if (match_is_empty(&current_match)) {
780             ELOG("Match is empty, ignoring this for_window statement\n");
781             break;
782         }
783         printf("\t should execute command %s for the criteria mentioned above\n", $3);
784         Assignment *assignment = scalloc(sizeof(Assignment));
785         assignment->type = A_COMMAND;
786         assignment->match = current_match;
787         assignment->dest.command = $3;
788         TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
789     }
790     ;
791
792 match:
793     | matchstart criteria matchend
794     {
795         printf("match parsed\n");
796     }
797     ;
798
799 matchstart:
800     '['
801     {
802         printf("start\n");
803         match_init(&current_match);
804     }
805     ;
806
807 matchend:
808     ']'
809     {
810         printf("match specification finished\n");
811     }
812     ;
813
814 criteria:
815     criteria criterion
816     | criterion
817     ;
818
819 criterion:
820     TOK_CLASS '=' STR
821     {
822         printf("criteria: class = %s\n", $3);
823         current_match.class = regex_new($3);
824         free($3);
825     }
826     | TOK_INSTANCE '=' STR
827     {
828         printf("criteria: instance = %s\n", $3);
829         current_match.instance = regex_new($3);
830         free($3);
831     }
832     | TOK_WINDOW_ROLE '=' STR
833     {
834         printf("criteria: window_role = %s\n", $3);
835         current_match.role = regex_new($3);
836         free($3);
837     }
838     | TOK_CON_ID '=' STR
839     {
840         printf("criteria: id = %s\n", $3);
841         char *end;
842         long parsed = strtol($3, &end, 10);
843         if (parsed == LONG_MIN ||
844             parsed == LONG_MAX ||
845             parsed < 0 ||
846             (end && *end != '\0')) {
847             ELOG("Could not parse con id \"%s\"\n", $3);
848         } else {
849             current_match.con_id = (Con*)parsed;
850             printf("id as int = %p\n", current_match.con_id);
851         }
852     }
853     | TOK_ID '=' STR
854     {
855         printf("criteria: window id = %s\n", $3);
856         char *end;
857         long parsed = strtol($3, &end, 10);
858         if (parsed == LONG_MIN ||
859             parsed == LONG_MAX ||
860             parsed < 0 ||
861             (end && *end != '\0')) {
862             ELOG("Could not parse window id \"%s\"\n", $3);
863         } else {
864             current_match.id = parsed;
865             printf("window id as int = %d\n", current_match.id);
866         }
867     }
868     | TOK_MARK '=' STR
869     {
870         printf("criteria: mark = %s\n", $3);
871         current_match.mark = regex_new($3);
872         free($3);
873     }
874     | TOK_TITLE '=' STR
875     {
876         printf("criteria: title = %s\n", $3);
877         current_match.title = regex_new($3);
878         free($3);
879     }
880     ;
881
882
883
884 word_or_number:
885     WORD
886     | NUMBER
887     {
888         asprintf(&$$, "%d", $1);
889     }
890     ;
891
892 mode:
893     TOKMODE QUOTEDSTRING '{' modelines '}'
894     {
895         if (strcasecmp($2, "default") == 0) {
896             printf("You cannot use the name \"default\" for your mode\n");
897             exit(1);
898         }
899         printf("\t now in mode %s\n", $2);
900         printf("\t current bindings = %p\n", current_bindings);
901         Binding *binding;
902         TAILQ_FOREACH(binding, current_bindings, bindings) {
903             printf("got binding on mods %d, keycode %d, symbol %s, command %s\n",
904                             binding->mods, binding->keycode, binding->symbol, binding->command);
905         }
906
907         struct Mode *mode = scalloc(sizeof(struct Mode));
908         mode->name = $2;
909         mode->bindings = current_bindings;
910         current_bindings = NULL;
911         SLIST_INSERT_HEAD(&modes, mode, modes);
912     }
913     ;
914
915
916 modelines:
917     /* empty */
918     | modelines modeline
919     ;
920
921 modeline:
922     comment
923     | binding
924     {
925         if (current_bindings == NULL) {
926             current_bindings = scalloc(sizeof(struct bindings_head));
927             TAILQ_INIT(current_bindings);
928         }
929
930         TAILQ_INSERT_TAIL(current_bindings, $1, bindings);
931     }
932     ;
933
934 bar:
935     TOK_BAR '{' barlines '}'
936     {
937         printf("\t new bar configuration finished, saving.\n");
938         /* Generate a unique ID for this bar */
939         current_bar.id = sstrdup("bar-XXXXXX");
940         /* This works similar to mktemp in that it replaces the last six X with
941          * random letters, but without the restriction that the given buffer
942          * has to contain a valid path name. */
943         char *x = current_bar.id + strlen("bar-");
944         while (*x != '\0') {
945             *(x++) = (rand() % 26) + 'a';
946         }
947
948         /* Copy the current (static) structure into a dynamically allocated
949          * one, then cleanup our static one. */
950         Barconfig *bar_config = scalloc(sizeof(Barconfig));
951         memcpy(bar_config, &current_bar, sizeof(Barconfig));
952         TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
953
954         memset(&current_bar, '\0', sizeof(Barconfig));
955     }
956     ;
957
958 barlines:
959     /* empty */
960     | barlines barline
961     ;
962
963 barline:
964     comment
965     | bar_status_command
966     | bar_output
967     | bar_tray_output
968     | bar_position
969     | bar_mode
970     | bar_font
971     | bar_workspace_buttons
972     | bar_verbose
973     | bar_socket_path
974     | bar_colors
975     | bar_color_background
976     | bar_color_statusline
977     | bar_color_focused_workspace
978     | bar_color_active_workspace
979     | bar_color_inactive_workspace
980     | bar_color_urgent_workspace
981     ;
982
983 bar_status_command:
984     TOK_BAR_STATUS_COMMAND STR
985     {
986         DLOG("should add status command %s\n", $2);
987         FREE(current_bar.status_command);
988         current_bar.status_command = $2;
989     }
990     ;
991
992 bar_output:
993     TOK_BAR_OUTPUT STR
994     {
995         DLOG("bar output %s\n", $2);
996         int new_outputs = current_bar.num_outputs + 1;
997         current_bar.outputs = srealloc(current_bar.outputs, sizeof(char*) * new_outputs);
998         current_bar.outputs[current_bar.num_outputs] = $2;
999         current_bar.num_outputs = new_outputs;
1000     }
1001     ;
1002
1003 bar_tray_output:
1004     TOK_BAR_TRAY_OUTPUT STR
1005     {
1006         DLOG("tray %s\n", $2);
1007         FREE(current_bar.tray_output);
1008         current_bar.tray_output = $2;
1009     }
1010     ;
1011
1012 bar_position:
1013     TOK_BAR_POSITION bar_position_position
1014     {
1015         DLOG("position %d\n", $2);
1016         current_bar.position = $2;
1017     }
1018     ;
1019
1020 bar_position_position:
1021     TOK_BAR_TOP      { $$ = P_TOP; }
1022     | TOK_BAR_BOTTOM { $$ = P_BOTTOM; }
1023     ;
1024
1025 bar_mode:
1026     TOK_BAR_MODE bar_mode_mode
1027     {
1028         DLOG("mode %d\n", $2);
1029         current_bar.mode = $2;
1030     }
1031     ;
1032
1033 bar_mode_mode:
1034     TOK_BAR_HIDE   { $$ = M_HIDE; }
1035     | TOK_BAR_DOCK { $$ = M_DOCK; }
1036     ;
1037
1038 bar_font:
1039     TOK_BAR_FONT STR
1040     {
1041         DLOG("font %s\n", $2);
1042         FREE(current_bar.font);
1043         current_bar.font = $2;
1044     }
1045     ;
1046
1047 bar_workspace_buttons:
1048     TOK_BAR_WORKSPACE_BUTTONS bool
1049     {
1050         DLOG("workspace_buttons = %d\n", $2);
1051         /* We store this inverted to make the default setting right when
1052          * initializing the struct with zero. */
1053         current_bar.hide_workspace_buttons = !($2);
1054     }
1055     ;
1056
1057 bar_verbose:
1058     TOK_BAR_VERBOSE bool
1059     {
1060         DLOG("verbose = %d\n", $2);
1061         current_bar.verbose = $2;
1062     }
1063     ;
1064
1065 bar_socket_path:
1066     TOK_BAR_SOCKET_PATH STR
1067     {
1068         DLOG("socket_path = %s\n", $2);
1069         FREE(current_bar.socket_path);
1070         current_bar.socket_path = $2;
1071     }
1072     ;
1073
1074 bar_colors:
1075     TOK_BAR_COLORS '{' barlines '}'
1076     {
1077         /* At the moment, the TOK_BAR_COLORS token is only to make the config
1078          * friendlier for humans. We might change this in the future if it gets
1079          * more complex. */
1080     }
1081     ;
1082
1083 bar_color_background:
1084     TOK_BAR_COLOR_BACKGROUND HEXCOLOR
1085     {
1086         DLOG("background = %s\n", $2);
1087         current_bar.colors.background = $2;
1088     }
1089     ;
1090
1091 bar_color_statusline:
1092     TOK_BAR_COLOR_STATUSLINE HEXCOLOR
1093     {
1094         DLOG("statusline = %s\n", $2);
1095         current_bar.colors.statusline = $2;
1096     }
1097     ;
1098
1099 bar_color_focused_workspace:
1100     TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR
1101     {
1102         DLOG("focused_ws = %s and %s\n", $2, $3);
1103         current_bar.colors.focused_workspace_text = $2;
1104         current_bar.colors.focused_workspace_bg = $3;
1105     }
1106     ;
1107
1108 bar_color_active_workspace:
1109     TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
1110     {
1111         DLOG("active_ws = %s and %s\n", $2, $3);
1112         current_bar.colors.active_workspace_text = $2;
1113         current_bar.colors.active_workspace_bg = $3;
1114     }
1115     ;
1116
1117 bar_color_inactive_workspace:
1118     TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
1119     {
1120         DLOG("inactive_ws = %s and %s\n", $2, $3);
1121         current_bar.colors.inactive_workspace_text = $2;
1122         current_bar.colors.inactive_workspace_bg = $3;
1123     }
1124     ;
1125
1126 bar_color_urgent_workspace:
1127     TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR
1128     {
1129         DLOG("urgent_ws = %s and %s\n", $2, $3);
1130         current_bar.colors.urgent_workspace_text = $2;
1131         current_bar.colors.urgent_workspace_bg = $3;
1132     }
1133     ;
1134
1135 floating_modifier:
1136     TOKFLOATING_MODIFIER binding_modifiers
1137     {
1138         DLOG("floating modifier = %d\n", $2);
1139         config.floating_modifier = $2;
1140     }
1141     ;
1142
1143 orientation:
1144     TOK_ORIENTATION direction
1145     {
1146         DLOG("New containers should start with split direction %d\n", $2);
1147         config.default_orientation = $2;
1148     }
1149     ;
1150
1151 direction:
1152     TOK_HORIZ       { $$ = HORIZ; }
1153     | TOK_VERT      { $$ = VERT; }
1154     | TOK_AUTO      { $$ = NO_ORIENTATION; }
1155     ;
1156
1157 workspace_layout:
1158     TOK_WORKSPACE_LAYOUT layout_mode
1159     {
1160         DLOG("new containers will be in mode %d\n", $2);
1161         config.default_layout = $2;
1162
1163 #if 0
1164         /* We also need to change the layout of the already existing
1165          * workspaces here. Workspaces may exist at this point because
1166          * of the other directives which are modifying workspaces
1167          * (setting the preferred screen or name). While the workspace
1168          * objects are already created, they have never been used.
1169          * Thus, the user very likely awaits the default container mode
1170          * to trigger in this case, regardless of where it is inside
1171          * his configuration file. */
1172         Workspace *ws;
1173         TAILQ_FOREACH(ws, workspaces, workspaces) {
1174                 if (ws->table == NULL)
1175                         continue;
1176                 switch_layout_mode(global_conn,
1177                                    ws->table[0][0],
1178                                    config.container_mode);
1179         }
1180 #endif
1181     }
1182     | TOK_WORKSPACE_LAYOUT TOKSTACKLIMIT TOKSTACKLIMIT NUMBER
1183     {
1184         DLOG("stack-limit %d with val %d\n", $3, $4);
1185         config.container_stack_limit = $3;
1186         config.container_stack_limit_value = $4;
1187
1188 #if 0
1189         /* See the comment above */
1190         Workspace *ws;
1191         TAILQ_FOREACH(ws, workspaces, workspaces) {
1192                 if (ws->table == NULL)
1193                         continue;
1194                 Container *con = ws->table[0][0];
1195                 con->stack_limit = config.container_stack_limit;
1196                 con->stack_limit_value = config.container_stack_limit_value;
1197         }
1198 #endif
1199     }
1200     ;
1201
1202 layout_mode:
1203     TOK_DEFAULT       { $$ = L_DEFAULT; }
1204     | TOK_STACKING    { $$ = L_STACKED; }
1205     | TOK_TABBED      { $$ = L_TABBED; }
1206     ;
1207
1208 new_window:
1209     TOKNEWWINDOW border_style
1210     {
1211         DLOG("new windows should start with border style %d\n", $2);
1212         config.default_border = $2;
1213     }
1214     ;
1215
1216 new_float:
1217     TOKNEWFLOAT border_style
1218     {
1219        DLOG("new floating windows should start with border style %d\n", $2);
1220        config.default_floating_border = $2;
1221     }
1222     ;
1223
1224 border_style:
1225     TOK_NORMAL      { $$ = BS_NORMAL; }
1226     | TOK_NONE      { $$ = BS_NONE; }
1227     | TOK_1PIXEL    { $$ = BS_1PIXEL; }
1228     ;
1229
1230 bool:
1231     NUMBER
1232     {
1233         $$ = ($1 == 1);
1234     }
1235     | WORD
1236     {
1237         DLOG("checking word \"%s\"\n", $1);
1238         $$ = (strcasecmp($1, "yes") == 0 ||
1239               strcasecmp($1, "true") == 0 ||
1240               strcasecmp($1, "on") == 0 ||
1241               strcasecmp($1, "enable") == 0 ||
1242               strcasecmp($1, "active") == 0);
1243     }
1244     ;
1245
1246 focus_follows_mouse:
1247     TOKFOCUSFOLLOWSMOUSE bool
1248     {
1249         DLOG("focus follows mouse = %d\n", $2);
1250         config.disable_focus_follows_mouse = !($2);
1251     }
1252     ;
1253
1254 force_focus_wrapping:
1255     TOK_FORCE_FOCUS_WRAPPING bool
1256     {
1257         DLOG("force focus wrapping = %d\n", $2);
1258         config.force_focus_wrapping = $2;
1259     }
1260     ;
1261
1262 force_xinerama:
1263     TOK_FORCE_XINERAMA bool
1264     {
1265         DLOG("force xinerama = %d\n", $2);
1266         config.force_xinerama = $2;
1267     }
1268     ;
1269
1270 workspace_back_and_forth:
1271     TOK_WORKSPACE_AUTO_BAF bool
1272     {
1273         DLOG("automatic workspace back-and-forth = %d\n", $2);
1274         config.workspace_auto_back_and_forth = $2;
1275     }
1276     ;
1277
1278 workspace_bar:
1279     TOKWORKSPACEBAR bool
1280     {
1281         DLOG("workspace bar = %d\n", $2);
1282         config.disable_workspace_bar = !($2);
1283     }
1284     ;
1285
1286 workspace:
1287     TOKWORKSPACE NUMBER TOKOUTPUT OUTPUT optional_workspace_name
1288     {
1289         int ws_num = $2;
1290         if (ws_num < 1) {
1291             DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
1292         } else {
1293             char *ws_name = NULL;
1294             if ($5 == NULL) {
1295                 asprintf(&ws_name, "%d", ws_num);
1296             } else {
1297                 ws_name = $5;
1298             }
1299
1300             DLOG("Should assign workspace %s to output %s\n", ws_name, $4);
1301             /* Check for earlier assignments of the same workspace so that we
1302              * don’t have assignments of a single workspace to different
1303              * outputs */
1304             struct Workspace_Assignment *assignment;
1305             bool duplicate = false;
1306             TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
1307                 if (strcasecmp(assignment->name, ws_name) == 0) {
1308                     ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
1309                          ws_name);
1310                     assignment->output = $4;
1311                     duplicate = true;
1312                 }
1313             }
1314             if (!duplicate) {
1315                 assignment = scalloc(sizeof(struct Workspace_Assignment));
1316                 assignment->name = ws_name;
1317                 assignment->output = $4;
1318                 TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
1319             }
1320         }
1321     }
1322     | TOKWORKSPACE NUMBER workspace_name
1323     {
1324         int ws_num = $2;
1325         if (ws_num < 1) {
1326             DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
1327         } else {
1328             DLOG("workspace name to: %s\n", $3);
1329 #if 0
1330             if ($<string>3 != NULL) {
1331                     workspace_set_name(workspace_get(ws_num - 1), $<string>3);
1332                     free($<string>3);
1333             }
1334 #endif
1335         }
1336     }
1337     ;
1338
1339 optional_workspace_name:
1340     /* empty */          { $$ = NULL; }
1341     | workspace_name     { $$ = $1; }
1342     ;
1343
1344 workspace_name:
1345     QUOTEDSTRING         { $$ = $1; }
1346     | STR                { $$ = $1; }
1347     | WORD               { $$ = $1; }
1348     ;
1349
1350 assign:
1351     TOKASSIGN window_class STR
1352     {
1353         /* This is the old, deprecated form of assignments. It’s provided for
1354          * compatibility in version (4.1, 4.2, 4.3) and will be removed
1355          * afterwards. It triggers an i3-nagbar warning starting from 4.1. */
1356         ELOG("You are using the old assign syntax (without criteria). "
1357              "Please see the User's Guide for the new syntax and fix "
1358              "your config file.\n");
1359         context->has_errors = true;
1360         printf("assignment of %s to *%s*\n", $2, $3);
1361         char *workspace = $3;
1362         char *criteria = $2;
1363
1364         Assignment *assignment = scalloc(sizeof(Assignment));
1365         Match *match = &(assignment->match);
1366         match_init(match);
1367
1368         char *separator = NULL;
1369         if ((separator = strchr(criteria, '/')) != NULL) {
1370             *(separator++) = '\0';
1371             char *pattern;
1372             if (asprintf(&pattern, "(?i)%s", separator) == -1) {
1373                 ELOG("asprintf failed\n");
1374                 break;
1375             }
1376             match->title = regex_new(pattern);
1377             free(pattern);
1378             printf("  title = %s\n", separator);
1379         }
1380         if (*criteria != '\0') {
1381             char *pattern;
1382             if (asprintf(&pattern, "(?i)%s", criteria) == -1) {
1383                 ELOG("asprintf failed\n");
1384                 break;
1385             }
1386             match->class = regex_new(pattern);
1387             free(pattern);
1388             printf("  class = %s\n", criteria);
1389         }
1390         free(criteria);
1391
1392         /* Compatibility with older versions: If the assignment target starts
1393          * with ~, we create the equivalent of:
1394          *
1395          * for_window [class="foo"] floating enable
1396          */
1397         if (*workspace == '~') {
1398             workspace++;
1399             if (*workspace == '\0') {
1400                 /* This assignment was *only* for floating */
1401                 assignment->type = A_COMMAND;
1402                 assignment->dest.command = sstrdup("floating enable");
1403                 TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
1404                 break;
1405             } else {
1406                 /* Create a new assignment and continue afterwards */
1407                 Assignment *floating = scalloc(sizeof(Assignment));
1408                 match_copy(&(floating->match), match);
1409                 floating->type = A_COMMAND;
1410                 floating->dest.command = sstrdup("floating enable");
1411                 TAILQ_INSERT_TAIL(&assignments, floating, assignments);
1412             }
1413         }
1414
1415         assignment->type = A_TO_WORKSPACE;
1416         assignment->dest.workspace = workspace;
1417         TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
1418     }
1419     | TOKASSIGN match STR
1420     {
1421         if (match_is_empty(&current_match)) {
1422             ELOG("Match is empty, ignoring this assignment\n");
1423             break;
1424         }
1425         printf("new assignment, using above criteria, to workspace %s\n", $3);
1426         Assignment *assignment = scalloc(sizeof(Assignment));
1427         assignment->match = current_match;
1428         assignment->type = A_TO_WORKSPACE;
1429         assignment->dest.workspace = $3;
1430         TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
1431     }
1432     ;
1433
1434 window_class:
1435     QUOTEDSTRING
1436     | STR_NG
1437     ;
1438
1439 ipcsocket:
1440     TOKIPCSOCKET STR
1441     {
1442         config.ipc_socket_path = $2;
1443     }
1444     ;
1445
1446 restart_state:
1447     TOKRESTARTSTATE STR
1448     {
1449         config.restart_state_path = $2;
1450     }
1451     ;
1452
1453 exec:
1454     TOKEXEC STR
1455     {
1456         struct Autostart *new = smalloc(sizeof(struct Autostart));
1457         new->command = $2;
1458         TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
1459     }
1460     ;
1461
1462 exec_always:
1463     TOKEXEC_ALWAYS STR
1464     {
1465         struct Autostart *new = smalloc(sizeof(struct Autostart));
1466         new->command = $2;
1467         TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
1468     }
1469     ;
1470
1471 terminal:
1472     TOKTERMINAL STR
1473     {
1474         ELOG("The terminal option is DEPRECATED and has no effect. "
1475             "Please remove it from your configuration file.\n");
1476     }
1477     ;
1478
1479 font:
1480     TOKFONT STR
1481     {
1482         config.font = load_font($2, true);
1483         printf("font %s\n", $2);
1484         free($2);
1485     }
1486     ;
1487
1488 single_color:
1489     TOKSINGLECOLOR colorpixel
1490     {
1491         uint32_t *dest = $1;
1492         *dest = $2;
1493     }
1494     ;
1495
1496 color:
1497     TOKCOLOR colorpixel colorpixel colorpixel
1498     {
1499         struct Colortriple *dest = $1;
1500
1501         dest->border = $2;
1502         dest->background = $3;
1503         dest->text = $4;
1504     }
1505     ;
1506
1507 colorpixel:
1508     HEXCOLOR
1509     {
1510         $$ = get_colorpixel($1);
1511         free($1);
1512     }
1513     ;
1514
1515
1516 binding_modifiers:
1517     /* NULL */                               { $$ = 0; }
1518     | binding_modifier
1519     | binding_modifiers '+' binding_modifier { $$ = $1 | $3; }
1520     | binding_modifiers '+'                  { $$ = $1; }
1521     ;
1522
1523 binding_modifier:
1524     MODIFIER        { $$ = $1; }
1525     | TOKCONTROL    { $$ = BIND_CONTROL; }
1526     | TOKSHIFT      { $$ = BIND_SHIFT; }
1527     ;
1528
1529 popup_during_fullscreen:
1530     TOK_POPUP_DURING_FULLSCREEN popup_setting
1531     {
1532         DLOG("popup_during_fullscreen setting: %d\n", $2);
1533         config.popup_during_fullscreen = $2;
1534     }
1535     ;
1536
1537 popup_setting:
1538     TOK_IGNORE              { $$ = PDF_IGNORE; }
1539     | TOK_LEAVE_FULLSCREEN  { $$ = PDF_LEAVE_FULLSCREEN; }
1540     ;