]> git.sur5r.net Git - i3/i3/blob - src/cfgparse.y
cleanup cmdparse lexer/parser (ignore whitespace, solves conflicts)
[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 <unistd.h>
9 #include <fcntl.h>
10 #include <limits.h>
11
12 #include "all.h"
13
14 static Match current_match;
15
16 typedef struct yy_buffer_state *YY_BUFFER_STATE;
17 extern int yylex(struct context *context);
18 extern int yyparse(void);
19 extern FILE *yyin;
20 YY_BUFFER_STATE yy_scan_string(const char *);
21
22 static struct bindings_head *current_bindings;
23 static struct context *context;
24
25 /* We don’t need yydebug for now, as we got decent error messages using
26  * yyerror(). Should you ever want to extend the parser, it might be handy
27  * to just comment it in again, so it stays here. */
28 //int yydebug = 1;
29
30 void yyerror(const char *error_message) {
31     ELOG("\n");
32     ELOG("CONFIG: %s\n", error_message);
33     ELOG("CONFIG: in file \"%s\", line %d:\n",
34         context->filename, context->line_number);
35     ELOG("CONFIG:   %s\n", context->line_copy);
36     ELOG("CONFIG:   ");
37     for (int c = 1; c <= context->last_column; c++)
38         if (c >= context->first_column)
39             printf("^");
40         else printf(" ");
41     printf("\n");
42     ELOG("\n");
43 }
44
45 int yywrap() {
46     return 1;
47 }
48
49 void parse_file(const char *f) {
50     SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
51     int fd, ret, read_bytes = 0;
52     struct stat stbuf;
53     char *buf;
54     FILE *fstr;
55     char buffer[1026], key[512], value[512];
56
57     if ((fd = open(f, O_RDONLY)) == -1)
58         die("Could not open configuration file: %s\n", strerror(errno));
59
60     if (fstat(fd, &stbuf) == -1)
61         die("Could not fstat file: %s\n", strerror(errno));
62
63     buf = scalloc((stbuf.st_size + 1) * sizeof(char));
64     while (read_bytes < stbuf.st_size) {
65         if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
66             die("Could not read(): %s\n", strerror(errno));
67         read_bytes += ret;
68     }
69
70     if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
71         die("Could not lseek: %s\n", strerror(errno));
72
73     if ((fstr = fdopen(fd, "r")) == NULL)
74         die("Could not fdopen: %s\n", strerror(errno));
75
76     while (!feof(fstr)) {
77         if (fgets(buffer, 1024, fstr) == NULL) {
78             if (feof(fstr))
79                 break;
80             die("Could not read configuration file\n");
81         }
82
83         /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
84         if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
85             key[0] == '#' || strlen(key) < 3)
86             continue;
87
88         if (strcasecmp(key, "set") == 0) {
89             if (value[0] != '$')
90                 die("Malformed variable assignment, name has to start with $\n");
91
92             /* get key/value for this variable */
93             char *v_key = value, *v_value;
94             if ((v_value = strstr(value, " ")) == NULL)
95                 die("Malformed variable assignment, need a value\n");
96
97             *(v_value++) = '\0';
98
99             struct Variable *new = scalloc(sizeof(struct Variable));
100             new->key = sstrdup(v_key);
101             new->value = sstrdup(v_value);
102             SLIST_INSERT_HEAD(&variables, new, variables);
103             DLOG("Got new variable %s = %s\n", v_key, v_value);
104             continue;
105         }
106     }
107     fclose(fstr);
108
109     /* For every custom variable, see how often it occurs in the file and
110      * how much extra bytes it requires when replaced. */
111     struct Variable *current, *nearest;
112     int extra_bytes = 0;
113     /* We need to copy the buffer because we need to invalidate the
114      * variables (otherwise we will count them twice, which is bad when
115      * 'extra' is negative) */
116     char *bufcopy = sstrdup(buf);
117     SLIST_FOREACH(current, &variables, variables) {
118         int extra = (strlen(current->value) - strlen(current->key));
119         char *next;
120         for (next = bufcopy;
121              (next = strcasestr(bufcopy + (next - bufcopy), current->key)) != NULL;
122              next += strlen(current->key)) {
123             *next = '_';
124             extra_bytes += extra;
125         }
126     }
127     FREE(bufcopy);
128
129     /* Then, allocate a new buffer and copy the file over to the new one,
130      * but replace occurences of our variables */
131     char *walk = buf, *destwalk;
132     char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
133     destwalk = new;
134     while (walk < (buf + stbuf.st_size)) {
135         /* Find the next variable */
136         SLIST_FOREACH(current, &variables, variables)
137             current->next_match = strcasestr(walk, current->key);
138         nearest = NULL;
139         int distance = stbuf.st_size;
140         SLIST_FOREACH(current, &variables, variables) {
141             if (current->next_match == NULL)
142                 continue;
143             if ((current->next_match - walk) < distance) {
144                 distance = (current->next_match - walk);
145                 nearest = current;
146             }
147         }
148         if (nearest == NULL) {
149             /* If there are no more variables, we just copy the rest */
150             strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
151             destwalk += (buf + stbuf.st_size) - walk;
152             *destwalk = '\0';
153             break;
154         } else {
155             /* Copy until the next variable, then copy its value */
156             strncpy(destwalk, walk, distance);
157             strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
158             walk += distance + strlen(nearest->key);
159             destwalk += distance + strlen(nearest->value);
160         }
161     }
162
163     yy_scan_string(new);
164
165     context = scalloc(sizeof(struct context));
166     context->filename = f;
167
168     if (yyparse() != 0) {
169         fprintf(stderr, "Could not parse configfile\n");
170         exit(1);
171     }
172
173     FREE(context->line_copy);
174     free(context);
175     free(new);
176     free(buf);
177
178     while (!SLIST_EMPTY(&variables)) {
179         current = SLIST_FIRST(&variables);
180         FREE(current->key);
181         FREE(current->value);
182         SLIST_REMOVE_HEAD(&variables, variables);
183         FREE(current);
184     }
185 }
186
187 %}
188
189 %expect 1
190 %error-verbose
191 %lex-param { struct context *context }
192
193 %union {
194     int number;
195     char *string;
196     uint32_t *single_color;
197     struct Colortriple *color;
198     Match *match;
199     struct Binding *binding;
200 }
201
202 %token  <number>        NUMBER                      "<number>"
203 %token  <string>        WORD                        "<word>"
204 %token  <string>        STR                         "<string>"
205 %token  <string>        STR_NG                      "<string (non-greedy)>"
206 %token  <string>        HEX                         "<hex>"
207 %token  <string>        OUTPUT                      "<RandR output>"
208 %token                  TOKBINDCODE
209 %token                  TOKTERMINAL
210 %token                  TOKCOMMENT                  "<comment>"
211 %token                  TOKFONT                     "font"
212 %token                  TOKBINDSYM                  "bindsym"
213 %token  <number>        MODIFIER                    "<modifier>"
214 %token                  TOKCONTROL                  "control"
215 %token                  TOKSHIFT                    "shift"
216 %token                  WHITESPACE                  "<whitespace>"
217 %token                  TOKFLOATING_MODIFIER        "floating_modifier"
218 %token  <string>        QUOTEDSTRING                "<quoted string>"
219 %token                  TOKWORKSPACE                "workspace"
220 %token                  TOKOUTPUT                   "output"
221 %token                  TOKASSIGN                   "assign"
222 %token                  TOKSET
223 %token                  TOKIPCSOCKET                "ipc_socket"
224 %token                  TOKRESTARTSTATE             "restart_state"
225 %token                  TOKEXEC                     "exec"
226 %token  <single_color>  TOKSINGLECOLOR
227 %token  <color>         TOKCOLOR
228 %token                  TOKARROW                    "→"
229 %token                  TOKMODE                     "mode"
230 %token                  TOK_ORIENTATION             "default_orientation"
231 %token                  TOK_HORIZ                   "horizontal"
232 %token                  TOK_VERT                    "vertical"
233 %token                  TOK_AUTO                    "auto"
234 %token                  TOK_WORKSPACE_LAYOUT        "workspace_layout"
235 %token                  TOKNEWWINDOW                "new_window"
236 %token                  TOK_NORMAL                  "normal"
237 %token                  TOK_NONE                    "none"
238 %token                  TOK_1PIXEL                  "1pixel"
239 %token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
240 %token                  TOKWORKSPACEBAR             "workspace_bar"
241 %token                  TOK_DEFAULT                 "default"
242 %token                  TOK_STACKING                "stacking"
243 %token                  TOK_TABBED                  "tabbed"
244 %token  <number>        TOKSTACKLIMIT               "stack-limit"
245 %token                  TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen"
246 %token                  TOK_IGNORE                  "ignore"
247 %token                  TOK_LEAVE_FULLSCREEN        "leave_fullscreen"
248 %token                  TOK_FOR_WINDOW              "for_window"
249
250 %token              TOK_MARK            "mark"
251 %token              TOK_CLASS           "class"
252 %token              TOK_ID              "id"
253 %token              TOK_CON_ID          "con_id"
254 %token              TOK_TITLE           "title"
255
256 %type   <binding>       binding
257 %type   <binding>       bindcode
258 %type   <binding>       bindsym
259 %type   <number>        binding_modifiers
260 %type   <number>        binding_modifier
261 %type   <number>        direction
262 %type   <number>        layout_mode
263 %type   <number>        border_style
264 %type   <number>        new_window
265 %type   <number>        colorpixel
266 %type   <number>        bool
267 %type   <number>        popup_setting
268 %type   <string>        command
269 %type   <string>        word_or_number
270 %type   <string>        optional_workspace_name
271 %type   <string>        workspace_name
272 %type   <string>        window_class
273 %type   <match>         assign_target
274
275 %%
276
277 lines: /* empty */
278     | lines WHITESPACE line
279     | lines error
280     | lines line
281     ;
282
283 line:
284     bindline
285     | for_window
286     | mode
287     | floating_modifier
288     | orientation
289     | workspace_layout
290     | new_window
291     | focus_follows_mouse
292     | workspace_bar
293     | workspace
294     | assign
295     | ipcsocket
296     | restart_state
297     | exec
298     | single_color
299     | color
300     | terminal
301     | font
302     | comment
303     | popup_during_fullscreen
304     ;
305
306 optwhitespace:
307     | WHITESPACE
308     ;
309
310 comment:
311     TOKCOMMENT
312     ;
313
314 command:
315     STR
316     ;
317
318 bindline:
319     binding
320     {
321         TAILQ_INSERT_TAIL(bindings, $1, bindings);
322     }
323     ;
324
325 binding:
326     TOKBINDCODE WHITESPACE bindcode         { $$ = $3; }
327     | TOKBINDSYM WHITESPACE bindsym         { $$ = $3; }
328     ;
329
330 bindcode:
331     binding_modifiers NUMBER WHITESPACE command
332     {
333         printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $4);
334         Binding *new = scalloc(sizeof(Binding));
335
336         new->keycode = $2;
337         new->mods = $1;
338         new->command = $4;
339
340         $$ = new;
341     }
342     ;
343
344 bindsym:
345     binding_modifiers word_or_number WHITESPACE command
346     {
347         printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $4);
348         Binding *new = scalloc(sizeof(Binding));
349
350         new->symbol = $2;
351         new->mods = $1;
352         new->command = $4;
353
354         $$ = new;
355     }
356     ;
357
358 for_window:
359     TOK_FOR_WINDOW WHITESPACE match WHITESPACE command
360     {
361         printf("\t should execute command %s for the criteria mentioned above\n", $5);
362         Assignment *assignment = scalloc(sizeof(Assignment));
363         assignment->type = A_COMMAND;
364         assignment->match = current_match;
365         assignment->dest.command = $5;
366         TAILQ_INSERT_TAIL(&real_assignments, assignment, real_assignments);
367     }
368     ;
369
370 match:
371     | matchstart optwhitespace criteria optwhitespace matchend
372     {
373         printf("match parsed\n");
374     }
375     ;
376
377 matchstart:
378     '['
379     {
380         printf("start\n");
381         match_init(&current_match);
382     }
383     ;
384
385 matchend:
386     ']'
387     {
388         printf("match specification finished\n");
389     }
390     ;
391
392 criteria:
393     TOK_CLASS '=' STR
394     {
395         printf("criteria: class = %s\n", $3);
396         current_match.class = $3;
397     }
398     | TOK_CON_ID '=' STR
399     {
400         printf("criteria: id = %s\n", $3);
401         char *end;
402         long parsed = strtol($3, &end, 10);
403         if (parsed == LONG_MIN ||
404             parsed == LONG_MAX ||
405             parsed < 0 ||
406             (end && *end != '\0')) {
407             ELOG("Could not parse con id \"%s\"\n", $3);
408         } else {
409             current_match.con_id = (Con*)parsed;
410             printf("id as int = %p\n", current_match.con_id);
411         }
412     }
413     | TOK_ID '=' STR
414     {
415         printf("criteria: window id = %s\n", $3);
416         char *end;
417         long parsed = strtol($3, &end, 10);
418         if (parsed == LONG_MIN ||
419             parsed == LONG_MAX ||
420             parsed < 0 ||
421             (end && *end != '\0')) {
422             ELOG("Could not parse window id \"%s\"\n", $3);
423         } else {
424             current_match.id = parsed;
425             printf("window id as int = %d\n", current_match.id);
426         }
427     }
428     | TOK_MARK '=' STR
429     {
430         printf("criteria: mark = %s\n", $3);
431         current_match.mark = $3;
432     }
433     | TOK_TITLE '=' STR
434     {
435         printf("criteria: title = %s\n", $3);
436         current_match.title = $3;
437     }
438     ;
439
440
441
442 word_or_number:
443     WORD
444     | NUMBER
445     {
446         asprintf(&$$, "%d", $1);
447     }
448     ;
449
450 mode:
451     TOKMODE WHITESPACE QUOTEDSTRING WHITESPACE '{' modelines '}'
452     {
453         if (strcasecmp($3, "default") == 0) {
454             printf("You cannot use the name \"default\" for your mode\n");
455             exit(1);
456         }
457         printf("\t now in mode %s\n", $3);
458         printf("\t current bindings = %p\n", current_bindings);
459         Binding *binding;
460         TAILQ_FOREACH(binding, current_bindings, bindings) {
461             printf("got binding on mods %d, keycode %d, symbol %s, command %s\n",
462                             binding->mods, binding->keycode, binding->symbol, binding->command);
463         }
464
465         struct Mode *mode = scalloc(sizeof(struct Mode));
466         mode->name = $3;
467         mode->bindings = current_bindings;
468         current_bindings = NULL;
469         SLIST_INSERT_HEAD(&modes, mode, modes);
470     }
471     ;
472
473
474 modelines:
475     /* empty */
476     | modelines modeline
477     ;
478
479 modeline:
480     WHITESPACE
481     | comment
482     | binding
483     {
484         if (current_bindings == NULL) {
485             current_bindings = scalloc(sizeof(struct bindings_head));
486             TAILQ_INIT(current_bindings);
487         }
488
489         TAILQ_INSERT_TAIL(current_bindings, $1, bindings);
490     }
491     ;
492
493 floating_modifier:
494     TOKFLOATING_MODIFIER WHITESPACE binding_modifiers
495     {
496         DLOG("floating modifier = %d\n", $3);
497         config.floating_modifier = $3;
498     }
499     ;
500
501 orientation:
502     TOK_ORIENTATION WHITESPACE direction
503     {
504         DLOG("New containers should start with split direction %d\n", $3);
505         config.default_orientation = $3;
506     }
507     ;
508
509 direction:
510     TOK_HORIZ       { $$ = HORIZ; }
511     | TOK_VERT      { $$ = VERT; }
512     | TOK_AUTO      { $$ = NO_ORIENTATION; }
513     ;
514
515 workspace_layout:
516     TOK_WORKSPACE_LAYOUT WHITESPACE layout_mode
517     {
518         DLOG("new containers will be in mode %d\n", $3);
519         config.default_layout = $3;
520
521 #if 0
522         /* We also need to change the layout of the already existing
523          * workspaces here. Workspaces may exist at this point because
524          * of the other directives which are modifying workspaces
525          * (setting the preferred screen or name). While the workspace
526          * objects are already created, they have never been used.
527          * Thus, the user very likely awaits the default container mode
528          * to trigger in this case, regardless of where it is inside
529          * his configuration file. */
530         Workspace *ws;
531         TAILQ_FOREACH(ws, workspaces, workspaces) {
532                 if (ws->table == NULL)
533                         continue;
534                 switch_layout_mode(global_conn,
535                                    ws->table[0][0],
536                                    config.container_mode);
537         }
538 #endif
539     }
540     | TOK_WORKSPACE_LAYOUT WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER
541     {
542         DLOG("stack-limit %d with val %d\n", $5, $7);
543         config.container_stack_limit = $5;
544         config.container_stack_limit_value = $7;
545
546 #if 0
547         /* See the comment above */
548         Workspace *ws;
549         TAILQ_FOREACH(ws, workspaces, workspaces) {
550                 if (ws->table == NULL)
551                         continue;
552                 Container *con = ws->table[0][0];
553                 con->stack_limit = config.container_stack_limit;
554                 con->stack_limit_value = config.container_stack_limit_value;
555         }
556 #endif
557     }
558     ;
559
560 layout_mode:
561     TOK_DEFAULT       { $$ = L_DEFAULT; }
562     | TOK_STACKING    { $$ = L_STACKED; }
563     | TOK_TABBED      { $$ = L_TABBED; }
564     ;
565
566 new_window:
567     TOKNEWWINDOW WHITESPACE border_style
568     {
569         DLOG("new windows should start with border style %d\n", $3);
570         config.default_border = $3;
571     }
572     ;
573
574 border_style:
575     TOK_NORMAL      { $$ = BS_NORMAL; }
576     | TOK_NONE      { $$ = BS_NONE; }
577     | TOK_1PIXEL    { $$ = BS_1PIXEL; }
578     ;
579
580 bool:
581     NUMBER
582     {
583         $$ = ($1 == 1);
584     }
585     | WORD
586     {
587         DLOG("checking word \"%s\"\n", $1);
588         $$ = (strcasecmp($1, "yes") == 0 ||
589               strcasecmp($1, "true") == 0 ||
590               strcasecmp($1, "on") == 0 ||
591               strcasecmp($1, "enable") == 0 ||
592               strcasecmp($1, "active") == 0);
593     }
594     ;
595
596 focus_follows_mouse:
597     TOKFOCUSFOLLOWSMOUSE WHITESPACE bool
598     {
599         DLOG("focus follows mouse = %d\n", $3);
600         config.disable_focus_follows_mouse = !($3);
601     }
602     ;
603
604 workspace_bar:
605     TOKWORKSPACEBAR WHITESPACE bool
606     {
607         DLOG("workspace bar = %d\n", $3);
608         config.disable_workspace_bar = !($3);
609     }
610     ;
611
612 workspace:
613     TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name
614     {
615         int ws_num = $3;
616         if (ws_num < 1) {
617             DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
618         } else {
619             char *ws_name = NULL;
620             if ($8 == NULL) {
621                 asprintf(&ws_name, "%d", ws_num);
622             } else {
623                 ws_name = $8;
624             }
625
626             DLOG("Should assign workspace %s to output %s\n", ws_name, $7);
627             struct Workspace_Assignment *assignment = scalloc(sizeof(struct Workspace_Assignment));
628             assignment->name = ws_name;
629             assignment->output = $7;
630             TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
631         }
632     }
633     | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name
634     {
635         int ws_num = $3;
636         if (ws_num < 1) {
637             DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
638         } else {
639             DLOG("workspace name to: %s\n", $5);
640 #if 0
641             if ($<string>5 != NULL) {
642                     workspace_set_name(workspace_get(ws_num - 1), $<string>5);
643                     free($<string>5);
644             }
645 #endif
646         }
647     }
648     ;
649
650 optional_workspace_name:
651     /* empty */                     { $$ = NULL; }
652     | WHITESPACE workspace_name     { $$ = $2; }
653     ;
654
655 workspace_name:
656     QUOTEDSTRING         { $$ = $1; }
657     | STR                { $$ = $1; }
658     | WORD               { $$ = $1; }
659     ;
660
661 assign:
662     TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target
663     {
664         printf("assignment of %s\n", $3);
665
666         struct Match *match = $6;
667
668         char *separator = NULL;
669         if ((separator = strchr($3, '/')) != NULL) {
670             *(separator++) = '\0';
671             match->title = sstrdup(separator);
672         }
673         if (*$3 != '\0')
674             match->class = sstrdup($3);
675         free($3);
676
677         printf("  class = %s\n", match->class);
678         printf("  title = %s\n", match->title);
679         if (match->insert_where == M_ASSIGN_WS)
680             printf("  to ws %s\n", match->target_ws);
681         TAILQ_INSERT_TAIL(&assignments, match, assignments);
682     }
683     ;
684
685 assign_target:
686     NUMBER
687     {
688         /* TODO: named workspaces */
689         Match *match = smalloc(sizeof(Match));
690         match_init(match);
691         match->insert_where = M_ASSIGN_WS;
692         asprintf(&(match->target_ws), "%d", $1);
693         $$ = match;
694     }
695     | '~'
696     {
697         /* TODO: compatiblity */
698 #if 0
699         struct Assignment *new = scalloc(sizeof(struct Assignment));
700         new->floating = ASSIGN_FLOATING_ONLY;
701         $<assignment>$ = new;
702 #endif
703     }
704     | '~' NUMBER
705     {
706         /* TODO: compatiblity */
707 #if 0
708         struct Assignment *new = scalloc(sizeof(struct Assignment));
709         new->workspace = $<number>2;
710         new->floating = ASSIGN_FLOATING;
711         $<assignment>$ = new;
712 #endif
713     }
714     ;
715
716 window_class:
717     QUOTEDSTRING
718     | STR_NG
719     ;
720
721 optional_arrow:
722     /* NULL */
723     | TOKARROW WHITESPACE
724     ;
725
726 ipcsocket:
727     TOKIPCSOCKET WHITESPACE STR
728     {
729         config.ipc_socket_path = $3;
730     }
731     ;
732
733 restart_state:
734     TOKRESTARTSTATE WHITESPACE STR
735     {
736         config.restart_state_path = $3;
737     }
738     ;
739
740 exec:
741     TOKEXEC WHITESPACE STR
742     {
743         struct Autostart *new = smalloc(sizeof(struct Autostart));
744         new->command = $3;
745         TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
746     }
747     ;
748
749 terminal:
750     TOKTERMINAL WHITESPACE STR
751     {
752         ELOG("The terminal option is DEPRECATED and has no effect. "
753             "Please remove it from your configuration file.\n");
754     }
755     ;
756
757 font:
758     TOKFONT WHITESPACE STR
759     {
760         config.font = load_font($3, true);
761         printf("font %s\n", $3);
762     }
763     ;
764
765 single_color:
766     TOKSINGLECOLOR WHITESPACE colorpixel
767     {
768         uint32_t *dest = $1;
769         *dest = $3;
770     }
771     ;
772
773 color:
774     TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel
775     {
776         struct Colortriple *dest = $1;
777
778         dest->border = $3;
779         dest->background = $5;
780         dest->text = $7;
781     }
782     ;
783
784 colorpixel:
785     '#' HEX
786     {
787         char *hex;
788         if (asprintf(&hex, "#%s", $2) == -1)
789             die("asprintf()");
790         $$ = get_colorpixel(hex);
791         free(hex);
792     }
793     ;
794
795
796 binding_modifiers:
797     /* NULL */                               { $$ = 0; }
798     | binding_modifier
799     | binding_modifiers '+' binding_modifier { $$ = $1 | $3; }
800     | binding_modifiers '+'                  { $$ = $1; }
801     ;
802
803 binding_modifier:
804     MODIFIER        { $$ = $1; }
805     | TOKCONTROL    { $$ = BIND_CONTROL; }
806     | TOKSHIFT      { $$ = BIND_SHIFT; }
807     ;
808
809 popup_during_fullscreen:
810     TOK_POPUP_DURING_FULLSCREEN WHITESPACE popup_setting
811     {
812         DLOG("popup_during_fullscreen setting: %d\n", $3);
813         config.popup_during_fullscreen = $3;
814     }
815     ;
816
817 popup_setting:
818     TOK_IGNORE              { $$ = PDF_IGNORE; }
819     | TOK_LEAVE_FULLSCREEN  { $$ = PDF_LEAVE_FULLSCREEN; }
820     ;