]> git.sur5r.net Git - i3/i3/blob - src/cfgparse.y
Don’t force wrapping when focusing in a direction would work (+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 <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 %error-verbose
190 %lex-param { struct context *context }
191
192 %union {
193     int number;
194     char *string;
195     uint32_t *single_color;
196     struct Colortriple *color;
197     Match *match;
198     struct Binding *binding;
199 }
200
201 %token  <number>        NUMBER                      "<number>"
202 %token  <string>        WORD                        "<word>"
203 %token  <string>        STR                         "<string>"
204 %token  <string>        STR_NG                      "<string (non-greedy)>"
205 %token  <string>        HEX                         "<hex>"
206 %token  <string>        OUTPUT                      "<RandR output>"
207 %token                  TOKBINDCODE
208 %token                  TOKTERMINAL
209 %token                  TOKCOMMENT                  "<comment>"
210 %token                  TOKFONT                     "font"
211 %token                  TOKBINDSYM                  "bindsym"
212 %token  <number>        MODIFIER                    "<modifier>"
213 %token                  TOKCONTROL                  "control"
214 %token                  TOKSHIFT                    "shift"
215 %token                  TOKFLOATING_MODIFIER        "floating_modifier"
216 %token  <string>        QUOTEDSTRING                "<quoted string>"
217 %token                  TOKWORKSPACE                "workspace"
218 %token                  TOKOUTPUT                   "output"
219 %token                  TOKASSIGN                   "assign"
220 %token                  TOKSET
221 %token                  TOKIPCSOCKET                "ipc_socket"
222 %token                  TOKRESTARTSTATE             "restart_state"
223 %token                  TOKEXEC                     "exec"
224 %token  <single_color>  TOKSINGLECOLOR
225 %token  <color>         TOKCOLOR
226 %token                  TOKARROW                    "→"
227 %token                  TOKMODE                     "mode"
228 %token                  TOK_ORIENTATION             "default_orientation"
229 %token                  TOK_HORIZ                   "horizontal"
230 %token                  TOK_VERT                    "vertical"
231 %token                  TOK_AUTO                    "auto"
232 %token                  TOK_WORKSPACE_LAYOUT        "workspace_layout"
233 %token                  TOKNEWWINDOW                "new_window"
234 %token                  TOK_NORMAL                  "normal"
235 %token                  TOK_NONE                    "none"
236 %token                  TOK_1PIXEL                  "1pixel"
237 %token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
238 %token                  TOK_FORCE_FOCUS_WRAPPING    "force_focus_wrapping"
239 %token                  TOKWORKSPACEBAR             "workspace_bar"
240 %token                  TOK_DEFAULT                 "default"
241 %token                  TOK_STACKING                "stacking"
242 %token                  TOK_TABBED                  "tabbed"
243 %token  <number>        TOKSTACKLIMIT               "stack-limit"
244 %token                  TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen"
245 %token                  TOK_IGNORE                  "ignore"
246 %token                  TOK_LEAVE_FULLSCREEN        "leave_fullscreen"
247 %token                  TOK_FOR_WINDOW              "for_window"
248
249 %token              TOK_MARK            "mark"
250 %token              TOK_CLASS           "class"
251 %token              TOK_ID              "id"
252 %token              TOK_CON_ID          "con_id"
253 %token              TOK_TITLE           "title"
254
255 %type   <binding>       binding
256 %type   <binding>       bindcode
257 %type   <binding>       bindsym
258 %type   <number>        binding_modifiers
259 %type   <number>        binding_modifier
260 %type   <number>        direction
261 %type   <number>        layout_mode
262 %type   <number>        border_style
263 %type   <number>        new_window
264 %type   <number>        colorpixel
265 %type   <number>        bool
266 %type   <number>        popup_setting
267 %type   <string>        command
268 %type   <string>        word_or_number
269 %type   <string>        optional_workspace_name
270 %type   <string>        workspace_name
271 %type   <string>        window_class
272
273 %%
274
275 lines: /* empty */
276     | lines error
277     | lines line
278     ;
279
280 line:
281     bindline
282     | for_window
283     | mode
284     | floating_modifier
285     | orientation
286     | workspace_layout
287     | new_window
288     | focus_follows_mouse
289     | force_focus_wrapping
290     | workspace_bar
291     | workspace
292     | assign
293     | ipcsocket
294     | restart_state
295     | exec
296     | single_color
297     | color
298     | terminal
299     | font
300     | comment
301     | popup_during_fullscreen
302     ;
303
304 comment:
305     TOKCOMMENT
306     ;
307
308 command:
309     STR
310     ;
311
312 bindline:
313     binding
314     {
315         TAILQ_INSERT_TAIL(bindings, $1, bindings);
316     }
317     ;
318
319 binding:
320     TOKBINDCODE bindcode         { $$ = $2; }
321     | TOKBINDSYM bindsym         { $$ = $2; }
322     ;
323
324 bindcode:
325     binding_modifiers NUMBER command
326     {
327         printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $3);
328         Binding *new = scalloc(sizeof(Binding));
329
330         new->keycode = $2;
331         new->mods = $1;
332         new->command = $3;
333
334         $$ = new;
335     }
336     ;
337
338 bindsym:
339     binding_modifiers word_or_number command
340     {
341         printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $3);
342         Binding *new = scalloc(sizeof(Binding));
343
344         new->symbol = $2;
345         new->mods = $1;
346         new->command = $3;
347
348         $$ = new;
349     }
350     ;
351
352 for_window:
353     TOK_FOR_WINDOW match command
354     {
355         printf("\t should execute command %s for the criteria mentioned above\n", $3);
356         Assignment *assignment = scalloc(sizeof(Assignment));
357         assignment->type = A_COMMAND;
358         assignment->match = current_match;
359         assignment->dest.command = $3;
360         TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
361     }
362     ;
363
364 match:
365     | matchstart criteria matchend
366     {
367         printf("match parsed\n");
368     }
369     ;
370
371 matchstart:
372     '['
373     {
374         printf("start\n");
375         match_init(&current_match);
376     }
377     ;
378
379 matchend:
380     ']'
381     {
382         printf("match specification finished\n");
383     }
384     ;
385
386 criteria:
387     TOK_CLASS '=' STR
388     {
389         printf("criteria: class = %s\n", $3);
390         current_match.class = $3;
391     }
392     | TOK_CON_ID '=' STR
393     {
394         printf("criteria: id = %s\n", $3);
395         char *end;
396         long parsed = strtol($3, &end, 10);
397         if (parsed == LONG_MIN ||
398             parsed == LONG_MAX ||
399             parsed < 0 ||
400             (end && *end != '\0')) {
401             ELOG("Could not parse con id \"%s\"\n", $3);
402         } else {
403             current_match.con_id = (Con*)parsed;
404             printf("id as int = %p\n", current_match.con_id);
405         }
406     }
407     | TOK_ID '=' STR
408     {
409         printf("criteria: window id = %s\n", $3);
410         char *end;
411         long parsed = strtol($3, &end, 10);
412         if (parsed == LONG_MIN ||
413             parsed == LONG_MAX ||
414             parsed < 0 ||
415             (end && *end != '\0')) {
416             ELOG("Could not parse window id \"%s\"\n", $3);
417         } else {
418             current_match.id = parsed;
419             printf("window id as int = %d\n", current_match.id);
420         }
421     }
422     | TOK_MARK '=' STR
423     {
424         printf("criteria: mark = %s\n", $3);
425         current_match.mark = $3;
426     }
427     | TOK_TITLE '=' STR
428     {
429         printf("criteria: title = %s\n", $3);
430         current_match.title = $3;
431     }
432     ;
433
434
435
436 word_or_number:
437     WORD
438     | NUMBER
439     {
440         asprintf(&$$, "%d", $1);
441     }
442     ;
443
444 mode:
445     TOKMODE QUOTEDSTRING '{' modelines '}'
446     {
447         if (strcasecmp($2, "default") == 0) {
448             printf("You cannot use the name \"default\" for your mode\n");
449             exit(1);
450         }
451         printf("\t now in mode %s\n", $2);
452         printf("\t current bindings = %p\n", current_bindings);
453         Binding *binding;
454         TAILQ_FOREACH(binding, current_bindings, bindings) {
455             printf("got binding on mods %d, keycode %d, symbol %s, command %s\n",
456                             binding->mods, binding->keycode, binding->symbol, binding->command);
457         }
458
459         struct Mode *mode = scalloc(sizeof(struct Mode));
460         mode->name = $2;
461         mode->bindings = current_bindings;
462         current_bindings = NULL;
463         SLIST_INSERT_HEAD(&modes, mode, modes);
464     }
465     ;
466
467
468 modelines:
469     /* empty */
470     | modelines modeline
471     ;
472
473 modeline:
474     comment
475     | binding
476     {
477         if (current_bindings == NULL) {
478             current_bindings = scalloc(sizeof(struct bindings_head));
479             TAILQ_INIT(current_bindings);
480         }
481
482         TAILQ_INSERT_TAIL(current_bindings, $1, bindings);
483     }
484     ;
485
486 floating_modifier:
487     TOKFLOATING_MODIFIER binding_modifiers
488     {
489         DLOG("floating modifier = %d\n", $2);
490         config.floating_modifier = $2;
491     }
492     ;
493
494 orientation:
495     TOK_ORIENTATION direction
496     {
497         DLOG("New containers should start with split direction %d\n", $2);
498         config.default_orientation = $2;
499     }
500     ;
501
502 direction:
503     TOK_HORIZ       { $$ = HORIZ; }
504     | TOK_VERT      { $$ = VERT; }
505     | TOK_AUTO      { $$ = NO_ORIENTATION; }
506     ;
507
508 workspace_layout:
509     TOK_WORKSPACE_LAYOUT layout_mode
510     {
511         DLOG("new containers will be in mode %d\n", $2);
512         config.default_layout = $2;
513
514 #if 0
515         /* We also need to change the layout of the already existing
516          * workspaces here. Workspaces may exist at this point because
517          * of the other directives which are modifying workspaces
518          * (setting the preferred screen or name). While the workspace
519          * objects are already created, they have never been used.
520          * Thus, the user very likely awaits the default container mode
521          * to trigger in this case, regardless of where it is inside
522          * his configuration file. */
523         Workspace *ws;
524         TAILQ_FOREACH(ws, workspaces, workspaces) {
525                 if (ws->table == NULL)
526                         continue;
527                 switch_layout_mode(global_conn,
528                                    ws->table[0][0],
529                                    config.container_mode);
530         }
531 #endif
532     }
533     | TOK_WORKSPACE_LAYOUT TOKSTACKLIMIT TOKSTACKLIMIT NUMBER
534     {
535         DLOG("stack-limit %d with val %d\n", $3, $4);
536         config.container_stack_limit = $3;
537         config.container_stack_limit_value = $4;
538
539 #if 0
540         /* See the comment above */
541         Workspace *ws;
542         TAILQ_FOREACH(ws, workspaces, workspaces) {
543                 if (ws->table == NULL)
544                         continue;
545                 Container *con = ws->table[0][0];
546                 con->stack_limit = config.container_stack_limit;
547                 con->stack_limit_value = config.container_stack_limit_value;
548         }
549 #endif
550     }
551     ;
552
553 layout_mode:
554     TOK_DEFAULT       { $$ = L_DEFAULT; }
555     | TOK_STACKING    { $$ = L_STACKED; }
556     | TOK_TABBED      { $$ = L_TABBED; }
557     ;
558
559 new_window:
560     TOKNEWWINDOW border_style
561     {
562         DLOG("new windows should start with border style %d\n", $2);
563         config.default_border = $2;
564     }
565     ;
566
567 border_style:
568     TOK_NORMAL      { $$ = BS_NORMAL; }
569     | TOK_NONE      { $$ = BS_NONE; }
570     | TOK_1PIXEL    { $$ = BS_1PIXEL; }
571     ;
572
573 bool:
574     NUMBER
575     {
576         $$ = ($1 == 1);
577     }
578     | WORD
579     {
580         DLOG("checking word \"%s\"\n", $1);
581         $$ = (strcasecmp($1, "yes") == 0 ||
582               strcasecmp($1, "true") == 0 ||
583               strcasecmp($1, "on") == 0 ||
584               strcasecmp($1, "enable") == 0 ||
585               strcasecmp($1, "active") == 0);
586     }
587     ;
588
589 focus_follows_mouse:
590     TOKFOCUSFOLLOWSMOUSE bool
591     {
592         DLOG("focus follows mouse = %d\n", $2);
593         config.disable_focus_follows_mouse = !($2);
594     }
595     ;
596
597 force_focus_wrapping:
598     TOK_FORCE_FOCUS_WRAPPING bool
599     {
600         DLOG("force focus wrapping = %d\n", $2);
601         config.force_focus_wrapping = $2;
602     }
603     ;
604
605 workspace_bar:
606     TOKWORKSPACEBAR bool
607     {
608         DLOG("workspace bar = %d\n", $2);
609         config.disable_workspace_bar = !($2);
610     }
611     ;
612
613 workspace:
614     TOKWORKSPACE NUMBER TOKOUTPUT OUTPUT optional_workspace_name
615     {
616         int ws_num = $2;
617         if (ws_num < 1) {
618             DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
619         } else {
620             char *ws_name = NULL;
621             if ($5 == NULL) {
622                 asprintf(&ws_name, "%d", ws_num);
623             } else {
624                 ws_name = $5;
625             }
626
627             DLOG("Should assign workspace %s to output %s\n", ws_name, $4);
628             struct Workspace_Assignment *assignment = scalloc(sizeof(struct Workspace_Assignment));
629             assignment->name = ws_name;
630             assignment->output = $4;
631             TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
632         }
633     }
634     | TOKWORKSPACE NUMBER workspace_name
635     {
636         int ws_num = $2;
637         if (ws_num < 1) {
638             DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
639         } else {
640             DLOG("workspace name to: %s\n", $3);
641 #if 0
642             if ($<string>3 != NULL) {
643                     workspace_set_name(workspace_get(ws_num - 1), $<string>3);
644                     free($<string>3);
645             }
646 #endif
647         }
648     }
649     ;
650
651 optional_workspace_name:
652     /* empty */          { $$ = NULL; }
653     | workspace_name     { $$ = $1; }
654     ;
655
656 workspace_name:
657     QUOTEDSTRING         { $$ = $1; }
658     | STR                { $$ = $1; }
659     | WORD               { $$ = $1; }
660     ;
661
662 assign:
663     TOKASSIGN window_class STR
664     {
665         printf("assignment of %s to *%s*\n", $2, $3);
666         char *workspace = $3;
667         char *criteria = $2;
668
669         Assignment *assignment = scalloc(sizeof(Assignment));
670         Match *match = &(assignment->match);
671         match_init(match);
672
673         char *separator = NULL;
674         if ((separator = strchr(criteria, '/')) != NULL) {
675             *(separator++) = '\0';
676             match->title = sstrdup(separator);
677         }
678         if (*criteria != '\0')
679             match->class = sstrdup(criteria);
680         free(criteria);
681
682         printf("  class = %s\n", match->class);
683         printf("  title = %s\n", match->title);
684
685         /* Compatibility with older versions: If the assignment target starts
686          * with ~, we create the equivalent of:
687          *
688          * for_window [class="foo"] mode floating
689          */
690         if (*workspace == '~') {
691             workspace++;
692             if (*workspace == '\0') {
693                 /* This assignment was *only* for floating */
694                 assignment->type = A_COMMAND;
695                 assignment->dest.command = sstrdup("mode floating");
696                 TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
697                 break;
698             } else {
699                 /* Create a new assignment and continue afterwards */
700                 Assignment *floating = scalloc(sizeof(Assignment));
701                 match_copy(&(floating->match), match);
702                 floating->type = A_COMMAND;
703                 floating->dest.command = sstrdup("mode floating");
704                 TAILQ_INSERT_TAIL(&assignments, floating, assignments);
705             }
706         }
707
708         assignment->type = A_TO_WORKSPACE;
709         assignment->dest.workspace = workspace;
710         TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
711     }
712     ;
713
714 window_class:
715     QUOTEDSTRING
716     | STR_NG
717     ;
718
719 ipcsocket:
720     TOKIPCSOCKET STR
721     {
722         config.ipc_socket_path = $2;
723     }
724     ;
725
726 restart_state:
727     TOKRESTARTSTATE STR
728     {
729         config.restart_state_path = $2;
730     }
731     ;
732
733 exec:
734     TOKEXEC STR
735     {
736         struct Autostart *new = smalloc(sizeof(struct Autostart));
737         new->command = $2;
738         TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
739     }
740     ;
741
742 terminal:
743     TOKTERMINAL STR
744     {
745         ELOG("The terminal option is DEPRECATED and has no effect. "
746             "Please remove it from your configuration file.\n");
747     }
748     ;
749
750 font:
751     TOKFONT STR
752     {
753         config.font = load_font($2, true);
754         printf("font %s\n", $2);
755     }
756     ;
757
758 single_color:
759     TOKSINGLECOLOR colorpixel
760     {
761         uint32_t *dest = $1;
762         *dest = $2;
763     }
764     ;
765
766 color:
767     TOKCOLOR colorpixel colorpixel colorpixel
768     {
769         struct Colortriple *dest = $1;
770
771         dest->border = $2;
772         dest->background = $3;
773         dest->text = $4;
774     }
775     ;
776
777 colorpixel:
778     '#' HEX
779     {
780         char *hex;
781         if (asprintf(&hex, "#%s", $2) == -1)
782             die("asprintf()");
783         $$ = get_colorpixel(hex);
784         free(hex);
785     }
786     ;
787
788
789 binding_modifiers:
790     /* NULL */                               { $$ = 0; }
791     | binding_modifier
792     | binding_modifiers '+' binding_modifier { $$ = $1 | $3; }
793     | binding_modifiers '+'                  { $$ = $1; }
794     ;
795
796 binding_modifier:
797     MODIFIER        { $$ = $1; }
798     | TOKCONTROL    { $$ = BIND_CONTROL; }
799     | TOKSHIFT      { $$ = BIND_SHIFT; }
800     ;
801
802 popup_during_fullscreen:
803     TOK_POPUP_DURING_FULLSCREEN popup_setting
804     {
805         DLOG("popup_during_fullscreen setting: %d\n", $2);
806         config.popup_during_fullscreen = $2;
807     }
808     ;
809
810 popup_setting:
811     TOK_IGNORE              { $$ = PDF_IGNORE; }
812     | TOK_LEAVE_FULLSCREEN  { $$ = PDF_LEAVE_FULLSCREEN; }
813     ;