config: drop custom INI parser; migrate to TOML configurations
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I6eabda96988b987d7397d6fc3cd47f2f6a6a6964
This commit is contained in:
parent
c84819b3e8
commit
40227627c1
2 changed files with 209 additions and 390 deletions
21
Makefile
21
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
|
||||
|
|
|
|||
594
src/config.c
594
src/config.c
|
|
@ -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) +
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue