401 lines
13 KiB
C
Raw Normal View History

2025-12-28 03:14:31 +01:00
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
2025-12-28 03:14:31 +01:00
* Configuration system implementation
*/
#include "config.h"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#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->focus_follow_delay_ms = 100;
2025-12-28 03:14:31 +01:00
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);
cfg->autostart_enabled = true;
cfg->autostart_xdg = true;
char *autostart_path = expand_path("~/.config/dwn/autostart.d");
if (autostart_path != NULL) {
strncpy(cfg->autostart_path, autostart_path, sizeof(cfg->autostart_path) - 1);
dwn_free(autostart_path);
}
2026-01-08 23:58:19 +01:00
char *services_path = expand_path("~/.config/dwn/services.d");
if (services_path != NULL) {
strncpy(cfg->services_path, services_path, sizeof(cfg->services_path) - 1);
dwn_free(services_path);
}
cfg->api_enabled = true;
cfg->api_port = 8777;
cfg->demo_step_delay_ms = 4000;
cfg->demo_ai_timeout_ms = 15000;
cfg->demo_window_timeout_ms = 5000;
2025-12-28 03:14:31 +01:00
}
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, "focus_follow_delay") == 0) {
int val = atoi(value);
cfg->focus_follow_delay_ms = (val >= 0 && val <= 1000) ? val : 100;
2025-12-28 03:14:31 +01:00
} 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);
}
} else if (strcmp(section, "autostart") == 0) {
if (strcmp(key, "enabled") == 0) {
cfg->autostart_enabled = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
} else if (strcmp(key, "xdg_autostart") == 0) {
cfg->autostart_xdg = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
} else if (strcmp(key, "path") == 0) {
char *expanded = expand_path(value);
if (expanded != NULL) {
strncpy(cfg->autostart_path, expanded, sizeof(cfg->autostart_path) - 1);
dwn_free(expanded);
}
}
2026-01-08 23:58:19 +01:00
} else if (strcmp(section, "services") == 0) {
if (strcmp(key, "path") == 0) {
char *expanded = expand_path(value);
if (expanded != NULL) {
strncpy(cfg->services_path, expanded, sizeof(cfg->services_path) - 1);
dwn_free(expanded);
}
}
} else if (strcmp(section, "api") == 0) {
if (strcmp(key, "enabled") == 0) {
cfg->api_enabled = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
} else if (strcmp(key, "port") == 0) {
cfg->api_port = atoi(value);
}
} else if (strcmp(section, "demo") == 0) {
if (strcmp(key, "step_delay") == 0) {
int val = atoi(value);
cfg->demo_step_delay_ms = (val >= 1000 && val <= 30000) ? val : 4000;
} else if (strcmp(key, "ai_timeout") == 0) {
int val = atoi(value);
cfg->demo_ai_timeout_ms = (val >= 5000 && val <= 60000) ? val : 15000;
} else if (strcmp(key, "window_timeout") == 0) {
int val = atoi(value);
cfg->demo_window_timeout_ms = (val >= 1000 && val <= 30000) ? val : 5000;
}
2025-12-28 03:14:31 +01:00
}
}
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);
}