/* * DWN - Desktop Window Manager * retoor * Configuration system implementation */ #include "config.h" #include "util.h" #include #include #include #include #define DEFAULT_PANEL_BG "#1a1a2e" #define DEFAULT_PANEL_FG "#e0e0e0" #define DEFAULT_WS_ACTIVE "#4a90d9" #define DEFAULT_WS_INACTIVE "#3a3a4e" #define DEFAULT_WS_URGENT "#d94a4a" #define DEFAULT_TITLE_FOCUSED_BG "#2d3a4a" #define DEFAULT_TITLE_FOCUSED_FG "#ffffff" #define DEFAULT_TITLE_UNFOCUSED_BG "#1a1a1a" #define DEFAULT_TITLE_UNFOCUSED_FG "#808080" #define DEFAULT_BORDER_FOCUSED "#4a90d9" #define DEFAULT_BORDER_UNFOCUSED "#333333" #define DEFAULT_NOTIFICATION_BG "#2a2a3e" #define DEFAULT_NOTIFICATION_FG "#ffffff" Config *config_create(void) { Config *cfg = dwn_calloc(1, sizeof(Config)); config_set_defaults(cfg); return cfg; } void config_destroy(Config *cfg) { if (cfg != NULL) { secure_wipe(cfg->openrouter_api_key, sizeof(cfg->openrouter_api_key)); secure_wipe(cfg->exa_api_key, sizeof(cfg->exa_api_key)); dwn_free(cfg); } } void config_set_defaults(Config *cfg) { if (cfg == NULL) { return; } strncpy(cfg->terminal, "xfce4-terminal", sizeof(cfg->terminal) - 1); strncpy(cfg->launcher, "dmenu_run", sizeof(cfg->launcher) - 1); strncpy(cfg->file_manager, "thunar", sizeof(cfg->file_manager) - 1); cfg->focus_mode = FOCUS_CLICK; cfg->show_decorations = true; cfg->border_width = DEFAULT_BORDER_WIDTH; cfg->title_height = DEFAULT_TITLE_HEIGHT; cfg->panel_height = DEFAULT_PANEL_HEIGHT; cfg->gap = DEFAULT_GAP; strncpy(cfg->font_name, "fixed", sizeof(cfg->font_name) - 1); cfg->default_master_ratio = 0.55f; cfg->default_master_count = 1; cfg->default_layout = LAYOUT_TILING; cfg->top_panel_enabled = true; cfg->bottom_panel_enabled = true; cfg->openrouter_api_key[0] = '\0'; cfg->exa_api_key[0] = '\0'; strncpy(cfg->ai_model, "google/gemini-2.0-flash-exp:free", sizeof(cfg->ai_model) - 1); cfg->ai_enabled = false; strncpy(cfg->config_path, "~/.config/dwn/config", sizeof(cfg->config_path) - 1); strncpy(cfg->log_path, "~/.local/share/dwn/dwn.log", sizeof(cfg->log_path) - 1); } typedef struct { Config *cfg; char current_section[64]; } ParseContext; static void handle_config_entry(const char *section, const char *key, const char *value, void *user_data) { ParseContext *ctx = (ParseContext *)user_data; Config *cfg = ctx->cfg; if (strcmp(section, "general") == 0) { if (strcmp(key, "terminal") == 0) { strncpy(cfg->terminal, value, sizeof(cfg->terminal) - 1); } else if (strcmp(key, "launcher") == 0) { strncpy(cfg->launcher, value, sizeof(cfg->launcher) - 1); } else if (strcmp(key, "file_manager") == 0) { strncpy(cfg->file_manager, value, sizeof(cfg->file_manager) - 1); } else if (strcmp(key, "focus_mode") == 0) { if (strcmp(value, "follow") == 0) { cfg->focus_mode = FOCUS_FOLLOW; } else { cfg->focus_mode = FOCUS_CLICK; } } else if (strcmp(key, "decorations") == 0) { cfg->show_decorations = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0); } } else if (strcmp(section, "appearance") == 0) { if (strcmp(key, "border_width") == 0) { int val = atoi(value); cfg->border_width = (val >= 0 && val <= 50) ? val : DEFAULT_BORDER_WIDTH; } else if (strcmp(key, "title_height") == 0) { int val = atoi(value); cfg->title_height = (val >= 0 && val <= 100) ? val : DEFAULT_TITLE_HEIGHT; } else if (strcmp(key, "panel_height") == 0) { int val = atoi(value); cfg->panel_height = (val >= 0 && val <= 100) ? val : DEFAULT_PANEL_HEIGHT; } else if (strcmp(key, "gap") == 0) { int val = atoi(value); cfg->gap = (val >= 0 && val <= 100) ? val : DEFAULT_GAP; } else if (strcmp(key, "font") == 0) { strncpy(cfg->font_name, value, sizeof(cfg->font_name) - 1); } } else if (strcmp(section, "layout") == 0) { if (strcmp(key, "master_ratio") == 0) { float val = atof(value); cfg->default_master_ratio = (val >= 0.1f && val <= 0.9f) ? val : 0.55f; } else if (strcmp(key, "master_count") == 0) { int val = atoi(value); cfg->default_master_count = (val >= 1 && val <= 10) ? val : 1; } else if (strcmp(key, "default") == 0) { if (strcmp(value, "floating") == 0) { cfg->default_layout = LAYOUT_FLOATING; } else if (strcmp(value, "monocle") == 0) { cfg->default_layout = LAYOUT_MONOCLE; } else { cfg->default_layout = LAYOUT_TILING; } } } else if (strcmp(section, "panels") == 0) { if (strcmp(key, "top") == 0) { cfg->top_panel_enabled = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0); } else if (strcmp(key, "bottom") == 0) { cfg->bottom_panel_enabled = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0); } } else if (strcmp(section, "ai") == 0) { if (strcmp(key, "model") == 0) { strncpy(cfg->ai_model, value, sizeof(cfg->ai_model) - 1); } else if (strcmp(key, "openrouter_api_key") == 0) { strncpy(cfg->openrouter_api_key, value, sizeof(cfg->openrouter_api_key) - 1); cfg->ai_enabled = true; } else if (strcmp(key, "exa_api_key") == 0) { strncpy(cfg->exa_api_key, value, sizeof(cfg->exa_api_key) - 1); } } } bool config_parse_ini(const char *path, ConfigCallback callback, void *user_data) { char *expanded = expand_path(path); FILE *f = fopen(expanded, "r"); dwn_free(expanded); if (f == NULL) { return false; } char line[1024]; char section[64] = ""; while (fgets(line, sizeof(line), f) != NULL) { char *trimmed = str_trim(line); if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') { continue; } if (trimmed[0] == '[') { char *end = strchr(trimmed, ']'); if (end != NULL) { *end = '\0'; strncpy(section, trimmed + 1, sizeof(section) - 1); } continue; } char *equals = strchr(trimmed, '='); if (equals != NULL) { *equals = '\0'; char *key = str_trim(trimmed); char *value = str_trim(equals + 1); size_t vlen = strlen(value); if (vlen >= 2 && ((value[0] == '"' && value[vlen-1] == '"') || (value[0] == '\'' && value[vlen-1] == '\''))) { value[vlen-1] = '\0'; value++; } callback(section, key, value, user_data); } } fclose(f); return true; } bool config_load(Config *cfg, const char *path) { if (cfg == NULL) { return false; } config_set_defaults(cfg); const char *config_path = path; if (config_path == NULL) { config_path = cfg->config_path; } ParseContext ctx = { .cfg = cfg }; char *expanded = expand_path(config_path); if (file_exists(expanded)) { LOG_INFO("Loading configuration from %s", expanded); config_parse_ini(expanded, handle_config_entry, &ctx); } else { LOG_INFO("No config file found at %s, using defaults", expanded); } dwn_free(expanded); const char *openrouter_key = getenv("OPENROUTER_API_KEY"); if (openrouter_key != NULL && openrouter_key[0] != '\0') { strncpy(cfg->openrouter_api_key, openrouter_key, sizeof(cfg->openrouter_api_key) - 1); cfg->ai_enabled = true; LOG_INFO("OpenRouter API key found in environment"); } const char *exa_key = getenv("EXA_API_KEY"); if (exa_key != NULL && exa_key[0] != '\0') { strncpy(cfg->exa_api_key, exa_key, sizeof(cfg->exa_api_key) - 1); } if (cfg->ai_enabled) { LOG_INFO("AI features enabled"); } return true; } bool config_reload(Config *cfg) { if (cfg == NULL) { return false; } secure_wipe(cfg->openrouter_api_key, sizeof(cfg->openrouter_api_key)); secure_wipe(cfg->exa_api_key, sizeof(cfg->exa_api_key)); return config_load(cfg, cfg->config_path); } const char *config_get_terminal(void) { if (dwn != NULL && dwn->config != NULL) { return dwn->config->terminal; } return "xterm"; } const char *config_get_launcher(void) { if (dwn != NULL && dwn->config != NULL) { return dwn->config->launcher; } return "dmenu_run"; } int config_get_border_width(void) { if (dwn != NULL && dwn->config != NULL) { return dwn->config->border_width; } return DEFAULT_BORDER_WIDTH; } int config_get_title_height(void) { if (dwn != NULL && dwn->config != NULL) { return dwn->config->title_height; } return DEFAULT_TITLE_HEIGHT; } int config_get_panel_height(void) { if (dwn != NULL && dwn->config != NULL) { return dwn->config->panel_height; } return DEFAULT_PANEL_HEIGHT; } int config_get_gap(void) { if (dwn != NULL && dwn->config != NULL) { return dwn->config->gap; } return DEFAULT_GAP; } const ColorScheme *config_get_colors(void) { if (dwn != NULL && dwn->config != NULL) { return &dwn->config->colors; } return NULL; } void config_init_colors(Config *cfg) { if (cfg == NULL || dwn == NULL || dwn->display == NULL) { return; } cfg->colors.panel_bg = parse_color(DEFAULT_PANEL_BG); cfg->colors.panel_fg = parse_color(DEFAULT_PANEL_FG); cfg->colors.workspace_active = parse_color(DEFAULT_WS_ACTIVE); cfg->colors.workspace_inactive = parse_color(DEFAULT_WS_INACTIVE); cfg->colors.workspace_urgent = parse_color(DEFAULT_WS_URGENT); cfg->colors.title_focused_bg = parse_color(DEFAULT_TITLE_FOCUSED_BG); cfg->colors.title_focused_fg = parse_color(DEFAULT_TITLE_FOCUSED_FG); cfg->colors.title_unfocused_bg = parse_color(DEFAULT_TITLE_UNFOCUSED_BG); cfg->colors.title_unfocused_fg = parse_color(DEFAULT_TITLE_UNFOCUSED_FG); cfg->colors.border_focused = parse_color(DEFAULT_BORDER_FOCUSED); cfg->colors.border_unfocused = parse_color(DEFAULT_BORDER_UNFOCUSED); cfg->colors.notification_bg = parse_color(DEFAULT_NOTIFICATION_BG); cfg->colors.notification_fg = parse_color(DEFAULT_NOTIFICATION_FG); }