notashelf /
40227627c1a3b0945213400878d0933308e8d985
chroma
publicLightweight wallpaper daemon for Wayland
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.gitCommit 40227627c1a3
tarballunverified2 files changed+209-390
@@ -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@@ -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,336 +223,221 @@ static int add_output_mapping(chroma_config_t *config, const char *output_name, return CHROMA_OK; } -// Initialize configuration with defaults-static void init_default_config(chroma_config_t *config) {- if (!config)- return;-- memset(config, 0, sizeof(chroma_config_t));-- config->daemon_mode = false;- config->mapping_count = 0;+// 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;+ } - // Set default scaling and filtering- config->default_scale_mode = CHROMA_SCALE_FILL;- config->default_filter_quality = CHROMA_FILTER_LINEAR;- config->default_anchor = CHROMA_ANCHOR_CENTER;- config->default_anchor_x = 50.0f; // center- config->default_anchor_y = 50.0f; // center+ 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;+ } - // Set default downsampling settings- config->enable_downsampling = true; // enable by default, performance etc.- config->max_output_width = 3840; // 4K width- config->max_output_height = 2160; // 4K height- config->min_scale_factor = 0.25f; // don't scale below 25%+ chroma_log("INFO", "Loading TOML configuration from: %s", config_file); - // Set default image path (can be overridden)- const char *home = getenv("HOME");- if (home) {- snprintf(config->default_image, sizeof(config->default_image),- "%s/.config/chroma/default.jpg", home);- } else {- strcpy(config->default_image, "/usr/share/pixmaps/chroma-default.jpg");+ // 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 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;+ // 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; } - // Skip empty lines and comments- char *trimmed = trim_whitespace(line);- if (*trimmed == '\0' || *trimmed == '#' || *trimmed == ';') {- return CHROMA_OK;+ // 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); } - // 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+ // 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); } - *equals = '\0';- char *key = trim_whitespace(trimmed);- char *value = trim_whitespace(equals + 1);+ // Parse anchor+ toml_datum_t anchor = toml_seek(result.toptab, "anchor");+ if (anchor.type == TOML_STRING) {+ config->default_anchor = parse_anchor(anchor.u.s);+ } - value = remove_quotes(value);+ // 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;+ } - if (*key == '\0' || *value == '\0') {- chroma_log("WARN", "Invalid config line %d: empty key or value",- line_number);- return CHROMA_OK;+ // 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 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);+ // Parse downsampling section+ toml_datum_t downsampling = toml_seek(result.toptab, "downsampling");+ if (downsampling.type == TOML_TABLE) {+ toml_datum_t ds_tab = downsampling; - 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;+ toml_datum_t enable = toml_seek(ds_tab, "enable");+ if (enable.type == TOML_BOOLEAN) {+ config->enable_downsampling = enable.u.boolean; } - 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);+ 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; }- } 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);++ 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; }- } 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;++ 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; }+ } - // 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;- }+ // 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; } - if (!mapping) {- chroma_log("WARN", "Output %s not found for property %s (line %d)",- output_name, property, line_number);- return CHROMA_OK;+ 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; } - 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);+ // 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; } - return CHROMA_OK;- }+ char *expanded_path = chroma_expand_path(image.u.s);+ const char *path_to_use = expanded_path ? expanded_path : image.u.s; - // Expand path before validation and storage- char *expanded_path = chroma_expand_path(value);- const char *path_to_validate = expanded_path ? expanded_path : value;+ // 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; - // 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);+ 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); }- 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); } + toml_free(result);++ chroma_log("INFO",+ "Loaded TOML configuration: %d output mappings, default image: %s",+ config->mapping_count, config->default_image);+ return CHROMA_OK; } -// Load configuration from file+// Initialize configuration with defaults+static void init_default_config(chroma_config_t *config) {+ if (!config)+ return;++ memset(config, 0, sizeof(chroma_config_t));++ config->daemon_mode = false;+ config->mapping_count = 0;++ // Set default scaling and filtering+ config->default_scale_mode = CHROMA_SCALE_FILL;+ config->default_filter_quality = CHROMA_FILTER_LINEAR;+ config->default_anchor = CHROMA_ANCHOR_CENTER;+ config->default_anchor_x = 50.0f; // center+ config->default_anchor_y = 50.0f; // center++ // Set default downsampling settings+ config->enable_downsampling = true; // enable by default, performance etc.+ config->max_output_width = 3840; // 4K width+ config->max_output_height = 2160; // 4K height+ config->min_scale_factor = 0.25f; // don't scale below 25%++ // Set default image path (can be overridden)+ const char *home = getenv("HOME");+ if (home) {+ snprintf(config->default_image, sizeof(config->default_image),+ "%s/.config/chroma/default.jpg", home);+ } else {+ strcpy(config->default_image, "/usr/share/pixmaps/chroma-default.jpg");+ }+}++// 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;- }- }-- 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++;- }+ // 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; } - fclose(file);-- if (parse_errors > 0) {- chroma_log("WARN", "Config file contained %d errors", parse_errors);- // Continue anyway with partial configuration+ // 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; } - 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) +