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