#include #include #include #include #include #include #include "any_log.h" #include "config.h" #include "util.h" #define ANY_INI_IMPLEMENT #include "any_ini.h" typedef enum { CONFIG_STRING, CONFIG_INT, CONFIG_UINT, CONFIG_DOUBLE, CONFIG_BOOL, CONFIG_COLOR, } config_type_t; typedef struct { const char *key; config_type_t type; size_t offset; } config_entry_t; static const config_entry_t bar_entries[] = { { "width", CONFIG_UINT, offsetof(config_t, width) }, { "height", CONFIG_UINT, offsetof(config_t, height) }, { "font", CONFIG_STRING, offsetof(config_t, font) }, { "monitor", CONFIG_STRING, offsetof(config_t, monitor) }, { "override_redirect", CONFIG_BOOL, offsetof(config_t, override_redirect) }, { "background", CONFIG_COLOR, offsetof(config_t, background) }, { 0 }, }; //static const config_entry_t block_entries[] = { // { "type", CONFIG_INT, offsetof(config_block_t, type) }, // { "x-padding", CONFIG_UINT, offsetof(config_block_t, x_padding) }, // { "y-padding", CONFIG_UINT, offsetof(config_block_t, y_padding) }, // { "text-color", CONFIG_STRING, offsetof(config_block_t, text_color) }, // { "text", CONFIG_STRING, offsetof(config_block_t, text) }, // { "color", CONFIG_STRING, offsetof(config_block_t, color) }, // { 0 }, //}; static bool config_read_string(const char *value, char **result) { if (value == NULL) value = ""; size_t start = 0, end = strlen(value); if (value[0] == '"' && value[end - 1] == '"') { start++; end--; } free(*result); *result = strslice(value, start, end); return true; } static bool config_read_int(const char *value, int *result) { char *end; long n = strtol(value, &end, 0); *result = n; if (n > INT_MAX || n < INT_MIN) log_debug("Integer %ld is out of range", n); return *end == '\0' && n != LONG_MIN && n != LONG_MAX; } static bool config_read_uint(const char *value, unsigned int *result) { char *end; unsigned long n = strtoul(value, &end, 0); *result = n; if (n > UINT_MAX) log_debug("Integer %lu is out of range", n); return *end == '\0' && n != ULONG_MAX; } static bool config_read_double(const char *value, double *result) { char *end; *result = strtod(value, &end); return *end == '\0'; } static bool config_read_bool(const char *value, bool *result) { size_t lenght = strlen(value); if (lenght == 5 && !strcmp(value, "false")) { *result = false; return true; } if (lenght == 4 && !strcmp(value, "true")) { *result = true; return true; } log_debug("Invalid bool value"); return false; } static bool config_read_color(const char *value, color_t *result) { if (value[0] == '#') { char *end; unsigned long n = strtoul(value + 1, &end, 16); if (end[0] != '\0') return false; int bpc = 0; switch (end - (value + 1)) { case 3: bpc = 4; n = (n << 4) | 0x0f; break; case 6: bpc = 8; n = (n << 8) | 0xff; break; case 4: bpc = 4; break; case 8: bpc = 8; break; default: return false; } const unsigned int single_max = (1 << bpc) - 1; result->r = ((n >> 3 * bpc) & single_max) / (double)single_max; result->g = ((n >> 2 * bpc) & single_max) / (double)single_max; result->b = ((n >> 1 * bpc) & single_max) / (double)single_max; result->a = ((n >> 0 * bpc) & single_max) / (double)single_max; return true; } log_debug("Invalid color '%s'", value); return false; } static bool config_read_entry(const config_entry_t *entries, void *result, int line, const char *section, const char *key, const char *value) { const char *types[] = { "string", "integer", "unsigned integer", "double float", }; for (int i = 0; entries[i].key != NULL; i++) { if (!strcmp(key, entries[i].key)) { bool nullable = entries[i].type == CONFIG_STRING; if ((value == NULL || *value == '\0') && !nullable) { log_value_error("Empty config entry", "s:section", section, "s:key", key, "s:expected_type", types[entries[i].type], "i:line", line); return false; } result += entries[i].offset; switch (entries[i].type) { case CONFIG_STRING: // NOTE: The previous string is freed by config_read_string if (config_read_string(value, (char **)result)) { log_debug("Set '%s.%s' to '%s'", section, key, *(char **)result); return true; } break; case CONFIG_INT: if (config_read_int(value, (int *)result)) { log_debug("Set '%s.%s' to '%d'", section, key, *(int *)result); return true; } break; case CONFIG_UINT: if (config_read_uint(value, (unsigned int *)result)) { log_debug("Set '%s.%s' to '%u'", section, key, *(unsigned int *)result); return true; } break; case CONFIG_DOUBLE: if (config_read_double(value, (double *)result)) { log_debug("Set '%s.%s' to '%lf'", section, key, *(double *)result); return true; } break; case CONFIG_BOOL: if (config_read_bool(value, (bool *)result)) { log_debug("Set '%s.%s' to '%s'", section, key, *(bool *)result ? "true" : "false"); return true; } break; case CONFIG_COLOR: if (config_read_color(value, (color_t *)result)) { char *color = color_to_string((color_t *)result); log_debug("Set '%s.%s' to '%s'", section, key, color); free(color); return true; } break; default: log_panic("Unreachable"); } log_value_error("Invalid config entry", "s:section", section, "s:key", key, "s:value", value, "s:expected_type", types[entries[i].type], "i:line", line); return false; } } log_value_warn("Unknown config entry", "s:section", section, "s:key", key, "s:value", value, "i:line", line); return true; } void config_init(config_t *config) { const config_t config_default = { .font = "monospace 10", .monitor = NULL, .height = 50, .width = 100, .override_redirect = false, }; memcpy(config, &config_default, sizeof(config_t)); config->font = strcopy(config_default.font); config->monitor = strcopy(config_default.monitor); } void config_read(config_t *config, FILE *file) { any_ini_stream_t ini; any_ini_file_init(&ini, file); int errors = 0; char *section = NULL; do { const config_entry_t *entries = NULL; void *result = NULL; config_section_t **section_ptr = NULL; size_t *section_size = NULL; char *section_label = NULL; if (section != NULL) { if (!strncmp(section, "block.", 6)) { section_ptr = &config->blocks; section_size = &config->n_blocks; section_label = strcopy(section + 6); } else if (!strncmp(section, "action.", 7)) { section_ptr = &config->actions; section_size = &config->n_actions; section_label = strcopy(section + 7); } else if (!strcmp(section, "bar")) { entries = bar_entries; result = config; } else log_warn("Unknown section '%s'", section); } if (section_ptr != NULL) { if (*section_label == '\0') { ++errors; log_value_error("Sections must have a non-empty label", "s:section", section, "i:line", ini.line); } *section_ptr = realloc(*section_ptr, ++(*section_size) * sizeof(config_section_t)); assert(*section_ptr != NULL); (*section_ptr)[*section_size - 1].label = section_label; } char *key; while ((key = any_ini_stream_next_key(&ini)) != NULL) { char *value = any_ini_stream_next_value(&ini); log_value_trace("Reading config entry", "s:section", section, "s:key", key, "s:value", value, "i:line", ini.line); if (entries != NULL) errors += !config_read_entry(entries, result, ini.line, section, key, value); free(key); free(value); } free(section); } while ((section = any_ini_stream_next_section(&ini)) != NULL); if (errors > 0) log_panic("Config file contained %d errors", errors); } void config_resolve(config_t *config, block_t *block) { int errors = 1; if (errors > 0) log_panic("Failed to resolve config"); } void config_free(config_t *config) { for (int i = 0; i < config->n_blocks; i++) { for (int j = 0; j < config->blocks[i].n_pairs; j++) pair_free(&config->blocks[i].pairs[j]); free(config->blocks[i].pairs); free(config->blocks[i].label); } for (int i = 0; i < config->n_actions; i++) { for (int j = 0; j < config->actions[i].n_pairs; j++) pair_free(&config->actions[i].pairs[j]); free(config->actions[i].pairs); free(config->actions[i].label); } free(config->blocks); free(config->actions); free(config->font); free(config->monitor); }