From 40227627c1a3b0945213400878d0933308e8d985 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Tue, 21 Apr 2026 17:25:16 +0300 Subject: [PATCH] config: drop custom INI parser; migrate to TOML configurations Signed-off-by: NotAShelf Change-Id: I6eabda96988b987d7397d6fc3cd47f2f6a6a6964 --- Makefile | 21 +- src/config.c | 594 +++++++++++++++++---------------------------------- 2 files changed, 217 insertions(+), 398 deletions(-) diff --git a/Makefile b/Makefile index d293bdc..c1bffd4 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/src/config.c b/src/config.c index 0c28132..d082249 100644 --- a/src/config.c +++ b/src/config.c @@ -5,29 +5,9 @@ #include #include -#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) +