diff --git a/Makefile b/Makefile index 419dc561..3baea500 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,9 @@ TOPDIR=$(shell pwd) include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files -FILES=$(patsubst %.c,%.o,$(wildcard src/*.c)) +AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c +FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c)) +FILES:=$(FILES:.c=.o) HEADERS=$(wildcard include/*.h) # Depend on the specific file (.c for each .o) and on all headers @@ -11,13 +13,23 @@ src/%.o: src/%.c ${HEADERS} echo "CC $<" $(CC) $(CFLAGS) -c -o $@ $< -all: ${FILES} +all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} echo "LINK i3" - $(CC) -o i3 ${FILES} $(LDFLAGS) + $(CC) -o i3 ${FILES} src/cfgparse.y.o src/cfgparse.yy.o $(LDFLAGS) echo "" echo "SUBDIR i3-msg" $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg +src/cfgparse.yy.o: src/cfgparse.l + echo "LEX $<" + lex -i -o $(@:.o=.c) $< + $(CC) $(CFLAGS) -c -o $@ $(@:.o=.c) + +src/cfgparse.y.o: src/cfgparse.y + echo "YACC $<" + yacc --debug --verbose -b $(basename $< .y) -d $< + $(CC) $(CFLAGS) -c -o $@ $(<:.y=.tab.c) + install: all echo "INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin @@ -46,7 +58,7 @@ dist: distclean rm -rf i3-${VERSION} clean: - rm -f src/*.o + rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c $(MAKE) -C docs clean $(MAKE) -C man clean $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean diff --git a/include/config.h b/include/config.h index 100faa4c..c0105cee 100644 --- a/include/config.h +++ b/include/config.h @@ -20,6 +20,7 @@ typedef struct Config Config; extern Config config; +extern bool config_use_lexer; /** * Part of the struct Config. It makes sense to group colors for background, @@ -40,6 +41,7 @@ struct Colortriple { struct Variable { char *key; char *value; + char *next_match; SLIST_ENTRY(Variable) variables; }; diff --git a/src/cfgparse.l b/src/cfgparse.l new file mode 100644 index 00000000..2332055a --- /dev/null +++ b/src/cfgparse.l @@ -0,0 +1,69 @@ +%{ +#include +#include "cfgparse.tab.h" +#include + +#include "data.h" +#include "config.h" +%} + +%Start BIND_COND +%Start BIND_AWS_COND +%Start BIND_A2WS_COND +%Start ASSIGN_COND +%Start COLOR_COND +%Start SCREEN_COND +%Start SCREEN_AWS_COND + +%% +[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } +^#[^\n]* { return TOKCOMMENT; } +[0-9]+ { yylval.number = atoi(yytext); return NUMBER; } +[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; } +bind { BEGIN(BIND_COND); return TOKBIND; } +bindsym { BEGIN(BIND_COND); return TOKBINDSYM; } +floating_modifier { return TOKFLOATING_MODIFIER; } +workspace { BEGIN(INITIAL); return TOKWORKSPACE; } +screen { BEGIN(SCREEN_COND); return TOKSCREEN; } +terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; } +font { BEGIN(BIND_AWS_COND); return TOKFONT; } +assign { BEGIN(ASSIGN_COND); return TOKASSIGN; } +set[^\n]* { return TOKCOMMENT; } +ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } +exec { BEGIN(BIND_AWS_COND); return TOKEXEC; } +client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } +client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; } +client.unfocused { BEGIN(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; } +client.urgent { BEGIN(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; } +bar.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } +bar.unfocused { BEGIN(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; } +bar.urgent { BEGIN(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; } +Mod1 { yylval.number = BIND_MOD1; return MODIFIER; } +Mod2 { yylval.number = BIND_MOD2; return MODIFIER; } +Mod3 { yylval.number = BIND_MOD3; return MODIFIER; } +Mod4 { yylval.number = BIND_MOD4; return MODIFIER; } +Mod5 { yylval.number = BIND_MOD5; return MODIFIER; } +Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; } +control { return TOKCONTROL; } +shift { return TOKSHIFT; } +→ { return TOKARROW; } +\n /* ignore end of line */; +x { return (int)yytext[0]; } +[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(SCREEN_AWS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } +[ \t]+ { return WHITESPACE; } +\"[^\"]+\" { + /* if ASSIGN_COND then */ + BEGIN(INITIAL); + /* yylval will be the string, but without quotes */ + char *copy = strdup(yytext+1); + copy[strlen(copy)-1] = '\0'; + yylval.string = copy; + return QUOTEDSTRING; + } +[^ \t]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR_NG; } +[a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; } +. { return (int)yytext[0]; } +%% diff --git a/src/cfgparse.y b/src/cfgparse.y new file mode 100644 index 00000000..4b7224b5 --- /dev/null +++ b/src/cfgparse.y @@ -0,0 +1,379 @@ +%{ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "data.h" +#include "config.h" +#include "i3.h" +#include "util.h" +#include "queue.h" +#include "table.h" + +extern int yylex(void); +extern FILE *yyin; + +int yydebug = 1; + +void yyerror(const char *str) { + fprintf(stderr,"error: %s\n",str); +} + +int yywrap() { + return 1; +} + +void parse_file(const char *f) { + SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); + int fd, ret, read_bytes = 0; + struct stat stbuf; + char *buf; + FILE *fstr; + char buffer[1026], key[512], value[512]; + + if ((fd = open(f, O_RDONLY)) == -1) + die("Could not open configuration file: %s\n", strerror(errno)); + + if (fstat(fd, &stbuf) == -1) + die("Could not fstat file: %s\n", strerror(errno)); + + buf = smalloc(stbuf.st_size * sizeof(char)); + while (read_bytes < stbuf.st_size) { + if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0) + die("Could not read(): %s\n", strerror(errno)); + read_bytes += ret; + } + + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) + die("Could not lseek: %s\n", strerror(errno)); + + if ((fstr = fdopen(fd, "r")) == NULL) + die("Could not fdopen: %s\n", strerror(errno)); + + while (!feof(fstr)) { + if (fgets(buffer, 1024, fstr) == NULL) { + if (feof(fstr)) + break; + die("Could not read configuration file\n"); + } + + /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ + if (sscanf(buffer, "%s %[^\n]", key, value) < 1 || + key[0] == '#' || strlen(key) < 3) + continue; + + if (strcasecmp(key, "set") == 0) { + if (value[0] != '$') + die("Malformed variable assignment, name has to start with $\n"); + + /* get key/value for this variable */ + char *v_key = value, *v_value; + if ((v_value = strstr(value, " ")) == NULL) + die("Malformed variable assignment, need a value\n"); + + *(v_value++) = '\0'; + + struct Variable *new = scalloc(sizeof(struct Variable)); + new->key = sstrdup(v_key); + new->value = sstrdup(v_value); + SLIST_INSERT_HEAD(&variables, new, variables); + LOG("Got new variable %s = %s\n", v_key, v_value); + continue; + } + } + + /* For every custom variable, see how often it occurs in the file and + * how much extra bytes it requires when replaced. */ + struct Variable *current, *nearest; + int extra_bytes = 0; + SLIST_FOREACH(current, &variables, variables) { + int extra = (strlen(current->value) - strlen(current->key)); + char *next; + for (next = buf; + (next = strcasestr(buf + (next - buf), current->key)) != NULL; + next += strlen(current->key)) + extra_bytes += extra; + } + + /* Then, allocate a new buffer and copy the file over to the new one, + * but replace occurences of our variables */ + char *walk = buf, *destwalk; + char *new = smalloc((stbuf.st_size + extra_bytes) * sizeof(char)); + destwalk = new; + while (walk < (buf + stbuf.st_size)) { + /* Find the next variable */ + SLIST_FOREACH(current, &variables, variables) + current->next_match = strcasestr(walk, current->key); + nearest = NULL; + int distance = stbuf.st_size; + SLIST_FOREACH(current, &variables, variables) { + if (current->next_match == NULL) + continue; + if ((current->next_match - walk) < distance) { + distance = (current->next_match - walk); + nearest = current; + } + } + if (nearest == NULL) { + /* If there are no more variables, we just copy the rest */ + strncpy(destwalk, walk, (buf + stbuf.st_size) - walk); + destwalk += (buf + stbuf.st_size) - walk; + *destwalk = '\0'; + break; + } else { + /* Copy until the next variable, then copy its value */ + strncpy(destwalk, walk, distance); + strncpy(destwalk + distance, nearest->value, strlen(nearest->value)); + walk += distance + strlen(nearest->key); + destwalk += distance + strlen(nearest->value); + } + } + + yy_scan_string(new); + + if (yyparse() != 0) { + fprintf(stderr, "Could not parse configfile\n"); + exit(1); + } + + free(new); + free(buf); +} + +%} + +%union { + int number; + char *string; + struct Colortriple *color; + struct Assignment *assignment; +} + +%token NUMBER +%token WORD +%token STR +%token STR_NG +%token HEX +%token TOKBIND +%token TOKTERMINAL +%token TOKCOMMENT +%token TOKFONT +%token TOKBINDSYM +%token MODIFIER +%token TOKCONTROL +%token TOKSHIFT +%token WHITESPACE +%token TOKFLOATING_MODIFIER +%token QUOTEDSTRING +%token TOKWORKSPACE +%token TOKSCREEN +%token TOKASSIGN +%token TOKSET +%token TOKIPCSOCKET +%token TOKEXEC +%token TOKCOLOR +%token TOKARROW + +%% + +lines: /* empty */ + | lines WHITESPACE line + | lines line + ; + +line: + bind + | bindsym + | floating_modifier + | workspace + | assign + | ipcsocket + | exec + | color + | terminal + | font + | comment + ; + +comment: + TOKCOMMENT + ; + +command: + STR + ; + +bind: + TOKBIND WHITESPACE binding_modifiers NUMBER WHITESPACE command + { + printf("\tFound binding mod%d with key %d and command %s\n", $3, $4, $6); + Binding *new = scalloc(sizeof(Binding)); + + new->keycode = $4; + new->mods = $3; + new->command = sstrdup($6); + + TAILQ_INSERT_TAIL(&bindings, new, bindings); + } + ; + +bindsym: + TOKBINDSYM WHITESPACE binding_modifiers WORD WHITESPACE command + { + printf("\tFound symbolic mod%d with key %s and command %s\n", $3, $4, $6); + Binding *new = scalloc(sizeof(Binding)); + + new->symbol = sstrdup($4); + new->mods = $3; + new->command = sstrdup($6); + + TAILQ_INSERT_TAIL(&bindings, new, bindings); + } + ; + +floating_modifier: + TOKFLOATING_MODIFIER WHITESPACE binding_modifiers + { + LOG("floating modifier = %d\n", $3); + config.floating_modifier = $3; + } + ; + +workspace: + TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKSCREEN WHITESPACE screen workspace_name + { + int ws_num = $3; + if (ws_num < 1 || ws_num > 10) { + LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); + } else { + workspaces[ws_num - 1].preferred_screen = sstrdup($7); + if ($8 != NULL) + workspace_set_name(&(workspaces[ws_num - 1]), $8); + } + } + ; + +workspace_name: + /* NULL */ { $$ = NULL; } + | WHITESPACE QUOTEDSTRING { $$ = $2; } + | WHITESPACE STR { $$ = $2; } + ; + +screen: + NUMBER { asprintf(&$$, "%d", $1); } + | NUMBER 'x' { asprintf(&$$, "%d", $1); } + | NUMBER 'x' NUMBER { asprintf(&$$, "%dx%d", $1, $3); } + | 'x' NUMBER { asprintf(&$$, "x%d", $2); } + ; + +assign: + TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target + { + printf("assignment of %s to %d\n", $3, $6); + + struct Assignment *new = $6; + new->windowclass_title = strdup($3); + TAILQ_INSERT_TAIL(&assignments, new, assignments); + } + ; + +assign_target: + NUMBER + { + struct Assignment *new = scalloc(sizeof(struct Assignment)); + new->workspace = $1; + new->floating = ASSIGN_FLOATING_NO; + $$ = new; + } + | '~' + { + struct Assignment *new = scalloc(sizeof(struct Assignment)); + new->floating = ASSIGN_FLOATING_ONLY; + $$ = new; + } + | '~' NUMBER + { + struct Assignment *new = scalloc(sizeof(struct Assignment)); + new->workspace = $2; + new->floating = ASSIGN_FLOATING; + $$ = new; + } + ; + +window_class: + QUOTEDSTRING + | STR_NG + ; + +optional_arrow: + /* NULL */ + | TOKARROW WHITESPACE + ; + +ipcsocket: + TOKIPCSOCKET WHITESPACE STR + { + config.ipc_socket_path = sstrdup($3); + } + ; + +exec: + TOKEXEC WHITESPACE STR + { + struct Autostart *new = smalloc(sizeof(struct Autostart)); + new->command = sstrdup($3); + TAILQ_INSERT_TAIL(&autostarts, new, autostarts); + } + ; + +terminal: + TOKTERMINAL WHITESPACE STR + { + config.terminal = sstrdup($3); + printf("terminal %s\n", config.terminal); + } + ; + +font: + TOKFONT WHITESPACE STR + { + config.font = sstrdup($3); + printf("font %s\n", config.font); + } + ; + + +color: + TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel + { + struct Colortriple *dest = $1; + + dest->border = $3; + dest->background = $5; + dest->text = $7; + } + ; + +colorpixel: + '#' HEX { $$ = get_colorpixel(global_conn, $2); } + ; + + +binding_modifiers: + /* NULL */ { $$ = 0; } + | binding_modifier + | binding_modifiers '+' binding_modifier { $$ = $1 | $3; } + | binding_modifiers '+' { $$ = $1; } + ; + +binding_modifier: + MODIFIER { $$ = $1; } + | TOKCONTROL { $$ = BIND_CONTROL; } + | TOKSHIFT { $$ = BIND_SHIFT; } + ; diff --git a/src/config.c b/src/config.c index dd2f70c1..ffc8b7ed 100644 --- a/src/config.c +++ b/src/config.c @@ -28,6 +28,8 @@ Config config; +bool config_use_lexer = false; + /* * This function resolves ~ in pathnames. * @@ -226,6 +228,25 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, config.bar.urgent.background = get_colorpixel(conn, "#900000"); config.bar.urgent.text = get_colorpixel(conn, "#ffffff"); + if (config_use_lexer) { + /* Yes, this will be cleaned up soon. */ + if (override_configpath != NULL) { + parse_file(override_configpath); + } else { + FILE *handle; + char *globbed = glob_path("~/.i3/config"); + if ((handle = fopen(globbed, "r")) == NULL) { + if ((handle = fopen("/etc/i3/config", "r")) == NULL) { + die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed); + } else { + parse_file("/etc/i3/config"); + } + } else { + parse_file(globbed); + } + } + } else { + FILE *handle; if (override_configpath != NULL) { if ((handle = fopen(override_configpath, "r")) == NULL) @@ -508,9 +529,6 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, grab_all_keys(conn); fclose(handle); - REQUIRED_OPTION(terminal); - REQUIRED_OPTION(font); - while (!SLIST_EMPTY(&variables)) { struct Variable *v = SLIST_FIRST(&variables); SLIST_REMOVE_HEAD(&variables, variables); @@ -518,6 +536,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, free(v->value); free(v); } + } + + REQUIRED_OPTION(terminal); + REQUIRED_OPTION(font); /* Set an empty name for every workspace which got no name */ for (int i = 0; i < 10; i++) { diff --git a/src/mainx.c b/src/mainx.c index db3d184a..329aae29 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -165,7 +165,7 @@ int main(int argc, char *argv[], char *env[]) { start_argv = argv; - while ((opt = getopt_long(argc, argv, "c:vah", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "c:vahl", long_options, &option_index)) != -1) { switch (opt) { case 'a': LOG("Autostart disabled using -a\n"); @@ -177,6 +177,9 @@ int main(int argc, char *argv[], char *env[]) { case 'v': printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); exit(EXIT_SUCCESS); + case 'l': + config_use_lexer = true; + break; default: fprintf(stderr, "Usage: %s [-c configfile] [-a] [-v]\n", argv[0]); fprintf(stderr, "\n");