X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fconfig_parser.c;h=e72923d6658905eee9521d7518fc896f66a923ae;hb=a6d8ed9b1ac6efa507d65b752758522bcfcc5213;hp=27cdeb93fe571cd5d2869ad4855f9e4e415d8236;hpb=31121f133456868d027bd4fcda3d5691641c3c67;p=i3%2Fi3 diff --git a/src/config_parser.c b/src/config_parser.c index 27cdeb93..e72923d6 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1,5 +1,3 @@ -#undef I3__FILE__ -#define I3__FILE__ "config_parser.c" /* * vim:ts=4:sw=4:expandtab * @@ -25,6 +23,8 @@ * nearest token. * */ +#include "all.h" + #include #include #include @@ -35,13 +35,14 @@ #include #include #include - -#include "all.h" +#include // Macros to make the YAJL API a bit easier to use. #define y(x, ...) yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__) #define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str)) +xcb_xrm_database_t *database = NULL; + #ifndef TEST_PARSER pid_t config_error_nagbar_pid = -1; static struct context *context; @@ -811,18 +812,80 @@ void start_config_error_nagbar(const char *configpath, bool has_errors) { free(pageraction); } +/* + * Inserts or updates a variable assignment depending on whether it already exists. + * + */ +static void upsert_variable(struct variables_head *variables, char *key, char *value) { + struct Variable *current; + SLIST_FOREACH(current, variables, variables) { + if (strcmp(current->key, key) != 0) { + continue; + } + + DLOG("Updated variable: %s = %s -> %s\n", key, current->value, value); + FREE(current->value); + current->value = sstrdup(value); + return; + } + + DLOG("Defined new variable: %s = %s\n", key, value); + struct Variable *new = scalloc(1, sizeof(struct Variable)); + struct Variable *test = NULL, *loc = NULL; + new->key = sstrdup(key); + new->value = sstrdup(value); + /* ensure that the correct variable is matched in case of one being + * the prefix of another */ + SLIST_FOREACH(test, variables, variables) { + if (strlen(new->key) >= strlen(test->key)) + break; + loc = test; + } + + if (loc == NULL) { + SLIST_INSERT_HEAD(variables, new, variables); + } else { + SLIST_INSERT_AFTER(loc, new, variables); + } +} + +static char *get_resource(char *name) { + if (conn == NULL) { + return NULL; + } + + /* Load the resource database lazily. */ + if (database == NULL) { + database = xcb_xrm_database_from_default(conn); + + if (database == NULL) { + ELOG("Failed to open the resource database.\n"); + + /* Load an empty database so we don't keep trying to load the + * default database over and over again. */ + database = xcb_xrm_database_from_string(""); + + return NULL; + } + } + + char *resource; + xcb_xrm_resource_get_string(database, name, NULL, &resource); + return resource; +} + /* * Parses the given file by first replacing the variables, then calling * parse_config and possibly launching i3-nagbar. * */ bool parse_file(const char *f, bool use_nagbar) { - SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); + struct variables_head variables = SLIST_HEAD_INITIALIZER(&variables); int fd; struct stat stbuf; char *buf; FILE *fstr; - char buffer[4096], key[512], value[512], *continuation = NULL; + char buffer[4096], key[512], value[4096], *continuation = NULL; if ((fd = open(f, O_RDONLY)) == -1) die("Could not open configuration file: %s\n", strerror(errno)); @@ -835,6 +898,11 @@ bool parse_file(const char *f, bool use_nagbar) { if ((fstr = fdopen(fd, "r")) == NULL) die("Could not fdopen: %s\n", strerror(errno)); + FREE(current_config); + current_config = scalloc(stbuf.st_size + 1, 1); + fread(current_config, 1, stbuf.st_size, fstr); + rewind(fstr); + while (!feof(fstr)) { if (!continuation) continuation = buffer; @@ -846,48 +914,86 @@ bool parse_file(const char *f, bool use_nagbar) { if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) { ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer)); } + + /* sscanf implicitly strips whitespace. */ + const bool skip_line = (sscanf(buffer, "%511s %4095[^\n]", key, value) < 1 || strlen(key) < 3); + const bool comment = (key[0] == '#'); + value[4095] = '\n'; + continuation = strstr(buffer, "\\\n"); if (continuation) { - continue; + if (!comment) { + continue; + } + DLOG("line continuation in comment is ignored: \"%.*s\"\n", (int)strlen(buffer) - 1, buffer); + continuation = NULL; } strncpy(buf + strlen(buf), buffer, strlen(buffer) + 1); - /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ - if (sscanf(buffer, "%511s %511[^\n]", key, value) < 1 || - key[0] == '#' || strlen(key) < 3) + /* Skip comments and empty lines. */ + if (skip_line || comment) { continue; + } if (strcasecmp(key, "set") == 0) { - if (value[0] != '$') { + char v_key[512]; + char v_value[4096]; + + if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) { + ELOG("Failed to parse variable specification '%s', skipping it.\n", value); + continue; + } + + if (v_key[0] != '$') { ELOG("Malformed variable assignment, name has to start with $\n"); continue; } - /* get key/value for this variable */ - char *v_key = value, *v_value; - if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) { - ELOG("Malformed variable assignment, need a value\n"); + upsert_variable(&variables, v_key, v_value); + continue; + } else if (strcasecmp(key, "set_from_resource") == 0) { + char res_name[512]; + char v_key[512]; + char fallback[4096]; + + /* Ensure that this string is terminated. For example, a user might + * want a variable to be empty if the resource can't be found and + * uses + * set_from_resource $foo i3wm.foo + * Without explicitly terminating the string first, sscanf() will + * leave it uninitialized, causing garbage in the config.*/ + fallback[0] = '\0'; + + if (sscanf(value, "%511s %511s %4095[^\n]", v_key, res_name, fallback) < 1) { + ELOG("Failed to parse resource specification '%s', skipping it.\n", value); continue; } - if (!(v_value = strstr(value, " "))) - v_value = strstr(value, "\t"); + if (v_key[0] != '$') { + ELOG("Malformed variable assignment, name has to start with $\n"); + continue; + } - *(v_value++) = '\0'; - while (*v_value == '\t' || *v_value == ' ') - v_value++; + char *res_value = get_resource(res_name); + if (res_value == NULL) { + DLOG("Could not get resource '%s', using fallback '%s'.\n", res_name, fallback); + res_value = sstrdup(fallback); + } - struct Variable *new = scalloc(1, sizeof(struct Variable)); - new->key = sstrdup(v_key); - new->value = sstrdup(v_value); - SLIST_INSERT_HEAD(&variables, new, variables); - DLOG("Got new variable %s = %s\n", v_key, v_value); + upsert_variable(&variables, v_key, res_value); + FREE(res_value); continue; } } fclose(fstr); + if (database != NULL) { + xcb_xrm_database_free(database); + /* Explicitly set the database to NULL again in case the config gets reloaded. */ + database = NULL; + } + /* 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; @@ -901,7 +1007,7 @@ bool parse_file(const char *f, bool use_nagbar) { char *next; for (next = bufcopy; next < (bufcopy + stbuf.st_size) && - (next = strcasestr(next, current->key)) != NULL; + (next = strcasestr(next, current->key)) != NULL; next += strlen(current->key)) { *next = '_'; extra_bytes += extra; @@ -910,7 +1016,7 @@ bool parse_file(const char *f, bool use_nagbar) { FREE(bufcopy); /* Then, allocate a new buffer and copy the file over to the new one, - * but replace occurences of our variables */ + * but replace occurrences of our variables */ char *walk = buf, *destwalk; char *new = smalloc(stbuf.st_size + extra_bytes + 1); destwalk = new;