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