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