2025-12-28 03:14:31 +01:00
|
|
|
/*
|
|
|
|
|
* DWN - Desktop Window Manager
|
2025-12-28 04:30:10 +01:00
|
|
|
* 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->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);
|
2025-12-28 09:26:53 +01:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
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, "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);
|
|
|
|
|
}
|
2025-12-28 09:26:53 +01:00
|
|
|
} 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
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);
|
|
|
|
|
}
|