config: drop custom INI parser; migrate to TOML configurations

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I6eabda96988b987d7397d6fc3cd47f2f6a6a6964
This commit is contained in:
raf 2026-04-21 17:25:16 +03:00
commit 40227627c1
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
2 changed files with 209 additions and 390 deletions

View file

@ -1,5 +1,5 @@
PROJECT_NAME = chroma
VERSION = 1.1.0
VERSION = 2.0.0
# Directories
SRCDIR = src
@ -14,6 +14,9 @@ PREFIX ?= /usr/local
BINDIR_INSTALL = $(PREFIX)/bin
SYSTEMD_INSTALL = $(HOME)/.config/systemd/user
# Config file
CONFIG_FILE_NAME = chroma.toml
# Compiler and flags
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -Werror -pedantic -O2 -g
@ -46,8 +49,11 @@ LDFLAGS += -lm -ldl
# Source files (excluding generated protocol files)
SOURCES = $(filter-out $(PROTOCOL_SOURCES), $(wildcard $(SRCDIR)/*.c))
VENDOR_SOURCES = $(INCDIR)/vendor/tomlc17.c
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(PROTOCOL_OBJECTS)
DEPENDS = $(OBJECTS:.o=.d)
VENDOR_OBJECTS = $(VENDOR_SOURCES:$(INCDIR)/vendor/%.c=$(OBJDIR)/%.o)
ALL_OBJECTS = $(OBJECTS) $(VENDOR_OBJECTS)
DEPENDS = $(ALL_OBJECTS:.o=.d)
# Override object files for image.c and render.c to suppress third-party warnings
OBJECTS := $(filter-out $(OBJDIR)/image.o $(OBJDIR)/render.o,$(OBJECTS))
@ -78,9 +84,9 @@ $(INCDIR):
@mkdir -p $(INCDIR)
# Build main executable
$(TARGET): version-header $(PROTOCOL_HEADERS) $(OBJECTS) | $(BINDIR)
$(TARGET): version-header $(PROTOCOL_HEADERS) $(ALL_OBJECTS) | $(BINDIR)
@echo " LINK $@"
@$(CC) $(OBJECTS) -o $@ $(LDFLAGS)
@$(CC) $(ALL_OBJECTS) -o $@ $(LDFLAGS)
# Compile source files
$(OBJDIR)/%.o: $(SRCDIR)/%.c $(PROTOCOL_HEADERS) | $(OBJDIR)
@ -95,6 +101,11 @@ $(OBJDIR)/render.o: $(SRCDIR)/render.c $(PROTOCOL_HEADERS) | $(OBJDIR)
@echo " CC $<"
@$(CC) $(CPPFLAGS) $(CFLAGS) -Wno-sign-conversion -Wno-double-promotion -Wno-conversion -MMD -MP -Wno-error -c $< -o $@
# Compile vendor files
$(OBJDIR)/%.o: $(INCDIR)/vendor/%.c | $(OBJDIR)
@echo " CC $<"
@$(CC) $(CPPFLAGS) $(CFLAGS) -w -MMD -MP -c $< -o $@
# Debug build
debug: CFLAGS = $(DEBUG_CFLAGS)
debug: $(TARGET)
@ -145,7 +156,7 @@ systemd-service: $(SYSTEMD_DIR)/$(PROJECT_NAME).service
# Create sample configuration file
sample-config:
@echo "Creating sample configuration..."
@cp chroma.conf.sample $(CONFIG_FILE_NAME)
@cp chroma.toml.sample $(CONFIG_FILE_NAME)
@echo "Sample configuration created at $(CONFIG_FILE_NAME)"
# Clean build artifacts

View file

@ -5,29 +5,9 @@
#include <string.h>
#include <strings.h>
#include "../include/chroma.h"
#include "chroma.h"
#include "vendor/tomlc17.h"
static char *trim_whitespace(char *str) {
char *end;
// Trim leading whitespace
while (isspace((unsigned char)*str))
str++;
// All spaces?
if (*str == 0)
return str;
// Trim trailing whitespace
end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end))
end--;
*(end + 1) = '\0';
return str;
}
// Remove quotes from a string
// Match output name/description against a config pattern
// Supports:
// - Exact name match: "DP-1" matches wl_output.name == "DP-1"
@ -60,31 +40,8 @@ static bool match_output(const char *pattern, const char *output_name,
return false;
}
static char *remove_quotes(char *str) {
size_t len = strlen(str);
if (len >= 2 && ((str[0] == '"' && str[len - 1] == '"') ||
(str[0] == '\'' && str[len - 1] == '\''))) {
str[len - 1] = '\0';
return str + 1;
}
return str;
}
// Parse boolean value from string
static bool parse_bool(const char *value) {
if (!value)
return false;
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0 ||
strcasecmp(value, "1") == 0 || strcasecmp(value, "on") == 0) {
return true;
}
return false;
}
// Parse scaling mode from string
static chroma_scale_mode_t parse_scale_mode(const char *value) {
chroma_scale_mode_t parse_scale_mode(const char *value) {
if (!value)
return CHROMA_SCALE_FILL; // default
@ -103,7 +60,7 @@ static chroma_scale_mode_t parse_scale_mode(const char *value) {
}
// Parse filter quality from string
static chroma_filter_quality_t parse_filter_quality(const char *value) {
chroma_filter_quality_t parse_filter_quality(const char *value) {
if (!value)
return CHROMA_FILTER_LINEAR; // default
@ -154,7 +111,7 @@ static const char *filter_quality_to_string(chroma_filter_quality_t quality) {
}
// Parse anchor position from string
static chroma_anchor_t parse_anchor(const char *value) {
chroma_anchor_t parse_anchor(const char *value) {
if (!value)
return CHROMA_ANCHOR_CENTER;
@ -213,12 +170,10 @@ static const char *anchor_to_string(chroma_anchor_t anchor) {
}
// Output-to-image mapping
static int add_output_mapping(chroma_config_t *config, const char *output_name,
const char *image_path,
chroma_scale_mode_t scale_mode,
chroma_filter_quality_t filter_quality,
chroma_anchor_t anchor, float anchor_x,
float anchor_y) {
int add_output_mapping(chroma_config_t *config, const char *output_name,
const char *image_path, chroma_scale_mode_t scale_mode,
chroma_filter_quality_t filter_quality,
chroma_anchor_t anchor, float anchor_x, float anchor_y) {
if (!config || !output_name || !image_path) {
return CHROMA_ERROR_INIT;
}
@ -268,6 +223,187 @@ static int add_output_mapping(chroma_config_t *config, const char *output_name,
return CHROMA_OK;
}
// Parse TOML configuration file
int chroma_config_load_toml(chroma_config_t *config, const char *config_file) {
if (!config || !config_file) {
return CHROMA_ERROR_INIT;
}
toml_result_t result = toml_parse_file_ex(config_file);
if (!result.ok) {
chroma_log("DEBUG", "TOML parse failed: %s", result.errmsg);
return CHROMA_ERROR_CONFIG;
}
chroma_log("INFO", "Loading TOML configuration from: %s", config_file);
// Parse default_image
toml_datum_t default_image = toml_seek(result.toptab, "default_image");
if (default_image.type == TOML_STRING) {
char *expanded_path = chroma_expand_path(default_image.u.s);
const char *path_to_use = expanded_path ? expanded_path : default_image.u.s;
if (strlen(path_to_use) < sizeof(config->default_image)) {
strcpy(config->default_image, path_to_use);
}
if (expanded_path) {
free(expanded_path);
}
}
// Parse daemon_mode
toml_datum_t daemon_mode = toml_seek(result.toptab, "daemon_mode");
if (daemon_mode.type == TOML_BOOLEAN) {
config->daemon_mode = daemon_mode.u.boolean;
}
// Parse scale_mode
toml_datum_t scale_mode = toml_seek(result.toptab, "scale_mode");
if (scale_mode.type == TOML_STRING) {
config->default_scale_mode = parse_scale_mode(scale_mode.u.s);
}
// Parse filter_quality
toml_datum_t filter_quality = toml_seek(result.toptab, "filter_quality");
if (filter_quality.type == TOML_STRING) {
config->default_filter_quality = parse_filter_quality(filter_quality.u.s);
}
// Parse anchor
toml_datum_t anchor = toml_seek(result.toptab, "anchor");
if (anchor.type == TOML_STRING) {
config->default_anchor = parse_anchor(anchor.u.s);
}
// Parse anchor_x
toml_datum_t anchor_x = toml_seek(result.toptab, "anchor_x");
if (anchor_x.type == TOML_INT64) {
config->default_anchor_x = (float)anchor_x.u.int64;
} else if (anchor_x.type == TOML_FP64) {
config->default_anchor_x = (float)anchor_x.u.fp64;
}
// Parse anchor_y
toml_datum_t anchor_y = toml_seek(result.toptab, "anchor_y");
if (anchor_y.type == TOML_INT64) {
config->default_anchor_y = (float)anchor_y.u.int64;
} else if (anchor_y.type == TOML_FP64) {
config->default_anchor_y = (float)anchor_y.u.fp64;
}
// Parse downsampling section
toml_datum_t downsampling = toml_seek(result.toptab, "downsampling");
if (downsampling.type == TOML_TABLE) {
toml_datum_t ds_tab = downsampling;
toml_datum_t enable = toml_seek(ds_tab, "enable");
if (enable.type == TOML_BOOLEAN) {
config->enable_downsampling = enable.u.boolean;
}
toml_datum_t max_width = toml_seek(ds_tab, "max_output_width");
if (max_width.type == TOML_INT64) {
config->max_output_width = (int)max_width.u.int64;
}
toml_datum_t max_height = toml_seek(ds_tab, "max_output_height");
if (max_height.type == TOML_INT64) {
config->max_output_height = (int)max_height.u.int64;
}
toml_datum_t min_scale = toml_seek(ds_tab, "min_scale_factor");
if (min_scale.type == TOML_FP64) {
config->min_scale_factor = (float)min_scale.u.fp64;
} else if (min_scale.type == TOML_INT64) {
config->min_scale_factor = (float)min_scale.u.int64;
}
}
// Parse output mappings array
toml_datum_t outputs = toml_seek(result.toptab, "output");
if (outputs.type == TOML_ARRAY) {
for (int i = 0; i < outputs.u.arr.size; i++) {
toml_datum_t output = outputs.u.arr.elem[i];
if (output.type != TOML_TABLE) {
continue;
}
toml_datum_t output_tab = output;
// Get output name
toml_datum_t name = toml_seek(output_tab, "name");
if (name.type != TOML_STRING) {
chroma_log("WARN", "Output mapping %d missing name", i);
continue;
}
// Get image path
toml_datum_t image = toml_seek(output_tab, "image");
if (image.type != TOML_STRING) {
chroma_log("WARN", "Output mapping %d missing image", i);
continue;
}
char *expanded_path = chroma_expand_path(image.u.s);
const char *path_to_use = expanded_path ? expanded_path : image.u.s;
// Get optional settings with defaults from global config
chroma_scale_mode_t out_scale = config->default_scale_mode;
chroma_filter_quality_t out_filter = config->default_filter_quality;
chroma_anchor_t out_anchor = config->default_anchor;
float out_anchor_x = config->default_anchor_x;
float out_anchor_y = config->default_anchor_y;
toml_datum_t out_scale_val = toml_seek(output_tab, "scale");
if (out_scale_val.type == TOML_STRING) {
out_scale = parse_scale_mode(out_scale_val.u.s);
}
toml_datum_t out_filter_val = toml_seek(output_tab, "filter");
if (out_filter_val.type == TOML_STRING) {
out_filter = parse_filter_quality(out_filter_val.u.s);
}
toml_datum_t out_anchor_val = toml_seek(output_tab, "anchor");
if (out_anchor_val.type == TOML_STRING) {
out_anchor = parse_anchor(out_anchor_val.u.s);
}
toml_datum_t out_anchor_x_val = toml_seek(output_tab, "anchor_x");
if (out_anchor_x_val.type == TOML_INT64) {
out_anchor_x = (float)out_anchor_x_val.u.int64;
} else if (out_anchor_x_val.type == TOML_FP64) {
out_anchor_x = (float)out_anchor_x_val.u.fp64;
}
toml_datum_t out_anchor_y_val = toml_seek(output_tab, "anchor_y");
if (out_anchor_y_val.type == TOML_INT64) {
out_anchor_y = (float)out_anchor_y_val.u.int64;
} else if (out_anchor_y_val.type == TOML_FP64) {
out_anchor_y = (float)out_anchor_y_val.u.fp64;
}
// Add the mapping
if (add_output_mapping(config, name.u.s, path_to_use, out_scale,
out_filter, out_anchor, out_anchor_x,
out_anchor_y) != CHROMA_OK) {
chroma_log("ERROR", "Failed to add TOML output mapping: %s", name.u.s);
}
if (expanded_path) {
free(expanded_path);
}
}
}
toml_free(result);
chroma_log("INFO",
"Loaded TOML configuration: %d output mappings, default image: %s",
config->mapping_count, config->default_image);
return CHROMA_OK;
}
// Initialize configuration with defaults
static void init_default_config(chroma_config_t *config) {
if (!config)
@ -301,303 +437,7 @@ static void init_default_config(chroma_config_t *config) {
}
}
// Parse a single configuration line
static int parse_config_line(chroma_config_t *config, char *line,
int line_number) {
if (!config || !line) {
return CHROMA_ERROR_INIT;
}
// Skip empty lines and comments
char *trimmed = trim_whitespace(line);
if (*trimmed == '\0' || *trimmed == '#' || *trimmed == ';') {
return CHROMA_OK;
}
// Find the equals sign
char *equals = strchr(trimmed, '=');
if (!equals) {
chroma_log("WARN", "Invalid config line %d: no '=' found", line_number);
return CHROMA_OK; // continue parsing
}
*equals = '\0';
char *key = trim_whitespace(trimmed);
char *value = trim_whitespace(equals + 1);
value = remove_quotes(value);
if (*key == '\0' || *value == '\0') {
chroma_log("WARN", "Invalid config line %d: empty key or value",
line_number);
return CHROMA_OK;
}
// Parse configuration options
if (strcasecmp(key, "default_image") == 0) {
char *expanded_path = chroma_expand_path(value);
const char *path_to_use = expanded_path ? expanded_path : value;
size_t path_len = strlen(path_to_use);
if (path_len >= sizeof(config->default_image)) {
chroma_log("ERROR", "Default image path too long: %s (max %zu)",
path_to_use, sizeof(config->default_image) - 1);
if (expanded_path) {
free(expanded_path);
}
return CHROMA_ERROR_CONFIG;
}
strcpy(config->default_image, path_to_use);
if (expanded_path) {
chroma_log("DEBUG", "Set default image: %s -> %s", value, expanded_path);
chroma_log("TRACE", "Default image path set: length=%zu, expanded='%s'",
path_len, expanded_path);
free(expanded_path);
} else {
chroma_log("WARN", "Failed to expand path, using original: %s", value);
}
} else if (strcasecmp(key, "daemon") == 0 ||
strcasecmp(key, "daemon_mode") == 0) {
config->daemon_mode = parse_bool(value);
chroma_log("DEBUG", "Set daemon mode: %s",
config->daemon_mode ? "true" : "false");
chroma_log("TRACE",
"Daemon mode configuration: key='%s', value='%s', parsed=%s",
key, value, config->daemon_mode ? "true" : "false");
} else if (strcasecmp(key, "scale_mode") == 0 ||
strcasecmp(key, "default_scale_mode") == 0) {
config->default_scale_mode = parse_scale_mode(value);
chroma_log("DEBUG", "Set default scale mode: %s",
scale_mode_to_string(config->default_scale_mode));
chroma_log("TRACE",
"Scale mode configuration: key='%s', value='%s', parsed=%s", key,
value, scale_mode_to_string(config->default_scale_mode));
} else if (strcasecmp(key, "filter_quality") == 0 ||
strcasecmp(key, "default_filter_quality") == 0) {
config->default_filter_quality = parse_filter_quality(value);
chroma_log("DEBUG", "Set default filter quality: %s",
filter_quality_to_string(config->default_filter_quality));
chroma_log("TRACE",
"Filter quality configuration: key='%s', value='%s', parsed=%s",
key, value,
filter_quality_to_string(config->default_filter_quality));
} else if (strcasecmp(key, "enable_downsampling") == 0) {
config->enable_downsampling = parse_bool(value);
chroma_log("DEBUG", "Set downsampling: %s",
config->enable_downsampling ? "enabled" : "disabled");
} else if (strcasecmp(key, "anchor") == 0) {
config->default_anchor = parse_anchor(value);
chroma_log("DEBUG", "Set default anchor: %s",
anchor_to_string(config->default_anchor));
} else if (strcasecmp(key, "anchor_x") == 0) {
char *endptr = NULL;
float ax = strtof(value, &endptr);
if (endptr == value || *endptr != '\0') {
chroma_log("WARN", "Invalid anchor_x: %s (not a number, using 50)",
value);
config->default_anchor_x = 50.0f;
} else if (ax < 0.0f || ax > 100.0f) {
chroma_log("WARN", "Invalid anchor_x: %s (range 0-100, using 50)", value);
config->default_anchor_x = 50.0f;
} else {
config->default_anchor_x = ax;
chroma_log("DEBUG", "Set default anchor_x: %.1f", (double)ax);
}
} else if (strcasecmp(key, "anchor_y") == 0) {
char *endptr = NULL;
float ay = strtof(value, &endptr);
if (endptr == value || *endptr != '\0') {
chroma_log("WARN", "Invalid anchor_y: %s (not a number, using 50)",
value);
config->default_anchor_y = 50.0f;
} else if (ay < 0.0f || ay > 100.0f) {
chroma_log("WARN", "Invalid anchor_y: %s (range 0-100, using 50)", value);
config->default_anchor_y = 50.0f;
} else {
config->default_anchor_y = ay;
chroma_log("DEBUG", "Set default anchor_y: %.1f", (double)ay);
}
} else if (strcasecmp(key, "max_output_width") == 0) {
int width = atoi(value);
if (width > 0 && width <= 16384) { // Reasonable limits
config->max_output_width = width;
chroma_log("DEBUG", "Set max output width: %d", width);
} else {
chroma_log("WARN", "Invalid max_output_width: %s (using 3840)", value);
}
} else if (strcasecmp(key, "max_output_height") == 0) {
int height = atoi(value);
if (height > 0 && height <= 16384) { // Reasonable limits
config->max_output_height = height;
chroma_log("DEBUG", "Set max output height: %d", height);
} else {
chroma_log("WARN", "Invalid max_output_height: %s (using 2160)", value);
}
} else if (strcasecmp(key, "min_scale_factor") == 0) {
float factor = (float)atof(value);
if (factor > 0.0f && factor <= 1.0f) {
config->min_scale_factor = factor;
chroma_log("DEBUG", "Set minimum scale factor: %.2f", (double)factor);
} else {
chroma_log("WARN", "Invalid min_scale_factor: %s (using 0.25)", value);
}
} else if (strncasecmp(key, "output.", 7) == 0) {
// Output-specific mapping: e.g., output.DP-1=/path/to/image.jpg
const char *output_name = key + 7;
if (*output_name == '\0') {
chroma_log("WARN", "Invalid output mapping line %d: no output name",
line_number);
return CHROMA_OK;
}
// Check for extended output configuration with properties. Format:
// output.DP-1.scale = fill
// output.DP-1.filter = linear
char *dot = strchr(output_name, '.');
if (dot) {
// This is an output property (scale or filter)
*dot = '\0';
const char *property = dot + 1;
// Find existing mapping for this output
chroma_config_mapping_t *mapping = NULL;
for (int i = 0; i < config->mapping_count; i++) {
if (strcmp(config->mappings[i].output_name, output_name) == 0) {
mapping = &config->mappings[i];
break;
}
}
if (!mapping) {
chroma_log("WARN", "Output %s not found for property %s (line %d)",
output_name, property, line_number);
return CHROMA_OK;
}
if (strcasecmp(property, "scale") == 0) {
mapping->scale_mode = parse_scale_mode(value);
chroma_log("DEBUG", "Set scale mode for output %s: %s", output_name,
scale_mode_to_string(mapping->scale_mode));
} else if (strcasecmp(property, "filter") == 0) {
mapping->filter_quality = parse_filter_quality(value);
chroma_log("DEBUG", "Set filter quality for output %s: %s", output_name,
filter_quality_to_string(mapping->filter_quality));
} else if (strcasecmp(property, "anchor") == 0) {
mapping->anchor = parse_anchor(value);
// Set anchor_x/anchor_y based on named anchor
switch (mapping->anchor) {
case CHROMA_ANCHOR_TOP:
mapping->anchor_x = 50.0f;
mapping->anchor_y = 0.0f;
break;
case CHROMA_ANCHOR_BOTTOM:
mapping->anchor_x = 50.0f;
mapping->anchor_y = 100.0f;
break;
case CHROMA_ANCHOR_LEFT:
mapping->anchor_x = 0.0f;
mapping->anchor_y = 50.0f;
break;
case CHROMA_ANCHOR_RIGHT:
mapping->anchor_x = 100.0f;
mapping->anchor_y = 50.0f;
break;
case CHROMA_ANCHOR_TOP_LEFT:
mapping->anchor_x = 0.0f;
mapping->anchor_y = 0.0f;
break;
case CHROMA_ANCHOR_TOP_RIGHT:
mapping->anchor_x = 100.0f;
mapping->anchor_y = 0.0f;
break;
case CHROMA_ANCHOR_BOTTOM_LEFT:
mapping->anchor_x = 0.0f;
mapping->anchor_y = 100.0f;
break;
case CHROMA_ANCHOR_BOTTOM_RIGHT:
mapping->anchor_x = 100.0f;
mapping->anchor_y = 100.0f;
break;
default:
mapping->anchor_x = 50.0f;
mapping->anchor_y = 50.0f;
break;
}
chroma_log("DEBUG", "Set anchor for output %s: %s (x=%.1f, y=%.1f)",
output_name, anchor_to_string(mapping->anchor),
(double)mapping->anchor_x, (double)mapping->anchor_y);
} else if (strcasecmp(property, "anchor_x") == 0) {
float ax = (float)atof(value);
if (ax >= 0.0f && ax <= 100.0f) {
mapping->anchor_x = ax;
chroma_log("DEBUG", "Set anchor_x for output %s: %.1f", output_name,
(double)ax);
} else {
mapping->anchor_x = 50.0f;
chroma_log("WARN", "Invalid anchor_x: %s (range 0-100, using 50)",
value);
}
} else if (strcasecmp(property, "anchor_y") == 0) {
float ay = (float)atof(value);
if (ay >= 0.0f && ay <= 100.0f) {
mapping->anchor_y = ay;
chroma_log("DEBUG", "Set anchor_y for output %s: %.1f", output_name,
(double)ay);
} else {
mapping->anchor_y = 50.0f;
chroma_log("WARN", "Invalid anchor_y: %s (range 0-100, using 50)",
value);
}
} else {
chroma_log("WARN", "Unknown output property: %s (line %d)", property,
line_number);
}
return CHROMA_OK;
}
// Expand path before validation and storage
char *expanded_path = chroma_expand_path(value);
const char *path_to_validate = expanded_path ? expanded_path : value;
// Validate image path
if (chroma_image_validate(path_to_validate) != CHROMA_OK) {
chroma_log("WARN", "Invalid image path for output %s: %s (expanded: %s)",
output_name, value, path_to_validate);
if (expanded_path) {
free(expanded_path);
}
return CHROMA_OK;
}
if (add_output_mapping(
config, output_name, path_to_validate, config->default_scale_mode,
config->default_filter_quality, config->default_anchor,
config->default_anchor_x, config->default_anchor_y) != CHROMA_OK) {
chroma_log("ERROR", "Failed to add output mapping: %s -> %s", output_name,
path_to_validate);
if (expanded_path) {
free(expanded_path);
}
return CHROMA_ERROR_CONFIG;
}
if (expanded_path) {
free(expanded_path);
}
} else {
chroma_log("WARN", "Unknown configuration key line %d: %s", line_number,
key);
chroma_log("TRACE", "Unrecognized config line %d: key='%s', value='%s'",
line_number, key, value);
}
return CHROMA_OK;
}
// Load configuration from file
// Load configuration from file (TOML format only)
int chroma_config_load(chroma_config_t *config, const char *config_file) {
if (!config) {
return CHROMA_ERROR_INIT;
@ -611,52 +451,20 @@ int chroma_config_load(chroma_config_t *config, const char *config_file) {
return CHROMA_OK;
}
FILE *file = fopen(config_file, "r");
if (!file) {
if (errno == ENOENT) {
chroma_log("INFO", "Config file not found: %s (using defaults)",
config_file);
return CHROMA_OK;
} else {
chroma_log("ERROR", "Failed to open config file %s: %s", config_file,
strerror(errno));
return CHROMA_ERROR_CONFIG;
}
// Check if file exists
if (!chroma_path_exists(config_file)) {
chroma_log("INFO", "Config file not found: %s (using defaults)",
config_file);
return CHROMA_OK;
}
chroma_log("INFO", "Loading configuration from: %s", config_file);
chroma_log("TRACE",
"Starting configuration parsing, estimated config size: %ld bytes",
chroma_get_file_size(config_file));
char line[1024];
int line_number = 0;
int parse_errors = 0;
while (fgets(line, sizeof(line), file)) {
line_number++;
char *newline = strchr(line, '\n');
if (newline) {
*newline = '\0';
}
if (parse_config_line(config, line, line_number) != CHROMA_OK) {
parse_errors++;
}
// Load TOML configuration
int result = chroma_config_load_toml(config, config_file);
if (result != CHROMA_OK) {
chroma_log("ERROR", "Failed to load TOML config from %s", config_file);
return result;
}
fclose(file);
if (parse_errors > 0) {
chroma_log("WARN", "Config file contained %d errors", parse_errors);
// Continue anyway with partial configuration
}
chroma_log("INFO",
"Loaded configuration: %d output mappings, default image: %s",
config->mapping_count, config->default_image);
// Log configuration memory usage
size_t config_size =
sizeof(chroma_config_t) +