Introduce support for specifying variables from X resources. (#2286)
This patch introduces a new 'set_from_resource' config directive which allows defining a variable by retrieving its value from the X resource database. This avoids having to configure a color scheme in multiple files. The directive takes an additional fallback value which is used in case the resource cannot be found or during config validation where no X connection is available. Furthermore, this patch includes the following changes: - If the same variable is defined twice, we now properly overwrite the value of the assignment rather than inserting two variable definitions with the same key. - We now depend on xcb-util-xrm to query the resource. - Increase the buffer size for variable / resource assignments. fixes #2130
This commit is contained in:
committed by
Michael Stapelberg
parent
dbafb3cf23
commit
47562b4143
@ -35,6 +35,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <xcb/xcb_xrm.h>
|
||||
|
||||
#include "all.h"
|
||||
|
||||
@ -42,6 +43,8 @@
|
||||
#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 +814,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));
|
||||
@ -848,8 +913,9 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
}
|
||||
|
||||
/* sscanf implicitly strips whitespace. */
|
||||
const bool skip_line = (sscanf(buffer, "%511s %511[^\n]", key, value) < 1 || strlen(key) < 3);
|
||||
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) {
|
||||
@ -868,49 +934,55 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
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");
|
||||
|
||||
*(v_value++) = '\0';
|
||||
while (*v_value == '\t' || *v_value == ' ')
|
||||
v_value++;
|
||||
|
||||
struct Variable *new = scalloc(1, sizeof(struct Variable));
|
||||
struct Variable *test = NULL, *loc = NULL;
|
||||
new->key = sstrdup(v_key);
|
||||
new->value = sstrdup(v_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 (v_key[0] != '$') {
|
||||
ELOG("Malformed variable assignment, name has to start with $\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (loc == NULL) {
|
||||
SLIST_INSERT_HEAD(&variables, new, variables);
|
||||
} else {
|
||||
SLIST_INSERT_AFTER(loc, new, variables);
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
Reference in New Issue
Block a user