#include #include #include #include #include #include "../include/chroma.h" // Default configuration values 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 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) { if (!value) return CHROMA_SCALE_FILL; // default if (strcasecmp(value, "fill") == 0) { return CHROMA_SCALE_FILL; } else if (strcasecmp(value, "fit") == 0) { return CHROMA_SCALE_FIT; } else if (strcasecmp(value, "stretch") == 0) { return CHROMA_SCALE_STRETCH; } else if (strcasecmp(value, "center") == 0) { return CHROMA_SCALE_CENTER; } chroma_log("WARN", "Unknown scaling mode: %s (using fill)", value); return CHROMA_SCALE_FILL; } // Parse filter quality from string static chroma_filter_quality_t parse_filter_quality(const char *value) { if (!value) return CHROMA_FILTER_LINEAR; // default if (strcasecmp(value, "nearest") == 0) { return CHROMA_FILTER_NEAREST; } else if (strcasecmp(value, "linear") == 0) { return CHROMA_FILTER_LINEAR; } else if (strcasecmp(value, "bilinear") == 0) { return CHROMA_FILTER_BILINEAR; } else if (strcasecmp(value, "trilinear") == 0) { return CHROMA_FILTER_TRILINEAR; } chroma_log("WARN", "Unknown filter quality: %s (using linear)", value); return CHROMA_FILTER_LINEAR; } // Get string representation of scaling mode static const char *scale_mode_to_string(chroma_scale_mode_t mode) { switch (mode) { case CHROMA_SCALE_FILL: return "fill"; case CHROMA_SCALE_FIT: return "fit"; case CHROMA_SCALE_STRETCH: return "stretch"; case CHROMA_SCALE_CENTER: return "center"; default: return "unknown"; } } // Get string representation of filter quality static const char *filter_quality_to_string(chroma_filter_quality_t quality) { switch (quality) { case CHROMA_FILTER_NEAREST: return "nearest"; case CHROMA_FILTER_LINEAR: return "linear"; case CHROMA_FILTER_BILINEAR: return "bilinear"; case CHROMA_FILTER_TRILINEAR: return "trilinear"; default: return "unknown"; } } // 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) { if (!config || !output_name || !image_path) { return CHROMA_ERROR_INIT; } if (config->mapping_count >= MAX_OUTPUTS) { chroma_log("ERROR", "Maximum number of output mappings reached (%d)", MAX_OUTPUTS); return CHROMA_ERROR_MEMORY; } // Validate string lengths to prevent buffer overflow size_t output_len = strlen(output_name); size_t path_len = strlen(image_path); if (output_len >= sizeof(config->mappings[0].output_name)) { chroma_log("ERROR", "Output name too long: %s (max %zu)", output_name, sizeof(config->mappings[0].output_name) - 1); return CHROMA_ERROR_CONFIG; } if (path_len >= sizeof(config->mappings[0].image_path)) { chroma_log("ERROR", "Image path too long: %s (max %zu)", image_path, sizeof(config->mappings[0].image_path) - 1); return CHROMA_ERROR_CONFIG; } chroma_config_mapping_t *mapping = &config->mappings[config->mapping_count]; strcpy(mapping->output_name, output_name); strcpy(mapping->image_path, image_path); mapping->scale_mode = scale_mode; mapping->filter_quality = filter_quality; config->mapping_count++; chroma_log("DEBUG", "Added mapping: %s -> %s (scale: %s, filter: %s)", output_name, image_path, scale_mode_to_string(scale_mode), filter_quality_to_string(filter_quality)); chroma_log("TRACE", "Output mapping %d: '%s' -> '%s' (path length: %zu)", config->mapping_count, output_name, image_path, path_len); 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; // Set default scaling and filtering config->default_scale_mode = CHROMA_SCALE_FILL; config->default_filter_quality = CHROMA_FILTER_LINEAR; // 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"); } } // 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, "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 = atof(value); if (factor > 0.0f && factor <= 1.0f) { // Valid range config->min_scale_factor = factor; chroma_log("DEBUG", "Set minimum scale factor: %.2f", 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 // Format: 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 { 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) != 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 int chroma_config_load(chroma_config_t *config, const char *config_file) { if (!config) { return CHROMA_ERROR_INIT; } // Initialize with defaults init_default_config(config); if (!config_file) { chroma_log("INFO", "No config file specified, using defaults"); 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++; } } 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) + (config->mapping_count * sizeof(chroma_config_mapping_t)); chroma_log_resource_allocation("config_data", config_size, "configuration structure"); chroma_log_memory_stats("post-config-load"); return CHROMA_OK; } // Free configuration resources void chroma_config_free(chroma_config_t *config) { if (!config) { return; } // Log configuration deallocation size_t config_size = sizeof(chroma_config_t) + (config->mapping_count * sizeof(chroma_config_mapping_t)); chroma_log_resource_deallocation("config_data", config_size, "configuration structure"); chroma_log("TRACE", "Clearing %d output mappings from configuration", config->mapping_count); memset(config->mappings, 0, sizeof(config->mappings)); config->mapping_count = 0; memset(config->default_image, 0, sizeof(config->default_image)); chroma_log("DEBUG", "Freed configuration"); } // Get image path for specific output const char *chroma_config_get_image_for_output(chroma_config_t *config, const char *output_name) { if (!config || !output_name) { return NULL; } // Look for specific output mapping for (int i = 0; i < config->mapping_count; i++) { if (strcmp(config->mappings[i].output_name, output_name) == 0) { chroma_log("DEBUG", "Found specific mapping for output %s: %s", output_name, config->mappings[i].image_path); return config->mappings[i].image_path; } } // Return default image if no specific mapping found if (strlen(config->default_image) > 0) { chroma_log("DEBUG", "Using default image for output %s: %s", output_name, config->default_image); return config->default_image; } chroma_log("WARN", "No image configured for output: %s", output_name); return NULL; } // Get configuration mapping for output, including scale mode and filter // quality int chroma_config_get_mapping_for_output( chroma_config_t *config, const char *output_name, chroma_scale_mode_t *scale_mode, chroma_filter_quality_t *filter_quality) { if (!config || !output_name || !scale_mode || !filter_quality) { return CHROMA_ERROR_INIT; } // Look for specific output mapping for (int i = 0; i < config->mapping_count; i++) { if (strcmp(config->mappings[i].output_name, output_name) == 0) { *scale_mode = config->mappings[i].scale_mode; *filter_quality = config->mappings[i].filter_quality; chroma_log("DEBUG", "Found specific mapping for output %s: scale=%s, filter=%s", output_name, scale_mode_to_string(*scale_mode), filter_quality_to_string(*filter_quality)); return CHROMA_OK; } } // Return defaults if no specific mapping found *scale_mode = config->default_scale_mode; *filter_quality = config->default_filter_quality; chroma_log("DEBUG", "Using defaults for output %s: scale=%s, filter=%s", output_name, scale_mode_to_string(*scale_mode), filter_quality_to_string(*filter_quality)); return CHROMA_OK; } // Print current configuration for debugging void chroma_config_print(const chroma_config_t *config) { if (!config) { return; } chroma_log("INFO", "=== Configuration ==="); chroma_log("INFO", "Default image: %s", config->default_image); chroma_log("INFO", "Daemon mode: %s", config->daemon_mode ? "true" : "false"); chroma_log("INFO", "Default scale mode: %s", scale_mode_to_string(config->default_scale_mode)); chroma_log("INFO", "Default filter quality: %s", filter_quality_to_string(config->default_filter_quality)); chroma_log("INFO", "Downsampling: %s", config->enable_downsampling ? "enabled" : "disabled"); if (config->enable_downsampling) { chroma_log("INFO", "Max output size: %dx%d", config->max_output_width, config->max_output_height); chroma_log("INFO", "Min scale factor: %.2f", config->min_scale_factor); } chroma_log("INFO", "Output mappings: %d", config->mapping_count); for (int i = 0; i < config->mapping_count; i++) { chroma_log("INFO", " %s -> %s (scale: %s, filter: %s)", config->mappings[i].output_name, config->mappings[i].image_path, scale_mode_to_string(config->mappings[i].scale_mode), filter_quality_to_string(config->mappings[i].filter_quality)); chroma_log( "TRACE", " Mapping %d: output='%s', image='%s', scale='%s', filter='%s', " "path_exists=%s", i, config->mappings[i].output_name, config->mappings[i].image_path, scale_mode_to_string(config->mappings[i].scale_mode), filter_quality_to_string(config->mappings[i].filter_quality), chroma_path_exists(config->mappings[i].image_path) ? "yes" : "no"); } chroma_log("INFO", "===================="); }