From 84ccdbec310e9de7885a5f9719e2fefba97bd6fe Mon Sep 17 00:00:00 2001 From: HAZARD Date: Mon, 3 Aug 2026 00:10:30 -0300 Subject: [PATCH 1/2] feat(config): add variable support to config parser - Introduce ConfigVar struct and Config fields var/var_count - Parse "var" option and store name/value pairs - Implement expand_config_vars to substitute {name} references - Update parse_config_line to expand values before parsing - Free allocated var resources in free_config Assisted-by: DeepSeek:deepseek-v4-flash --- src/config/parse_config.h | 136 +++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index d8f322359..8cff2f98f 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -59,6 +59,11 @@ typedef struct { char *value; } ConfigEnv; +typedef struct { + char *name; + char *value; +} ConfigVar; + typedef struct { const char *id; const char *title; @@ -391,6 +396,9 @@ typedef struct { ConfigEnv **env; int32_t env_count; + ConfigVar **var; + int32_t var_count; + char **exec; int32_t exec_count; @@ -2594,6 +2602,37 @@ bool parse_option(Config *config, char *key, char *value, int line_number) { } config->window_rules_count++; return !parse_error; + } else if (strcmp(key, "var") == 0) { + + char var_name[256], var_value[256]; + if (sscanf(value, "%255[^,],%255[^\n]", var_name, var_value) < 2) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid var format: " + "\033[1m\033[31m%s\n", + value); + return false; + } + trim_whitespace(var_name); + trim_whitespace(var_value); + + ConfigVar *var = calloc(1, sizeof(ConfigVar)); + var->name = strdup(var_name); + var->value = strdup(var_value); + + config->var = + realloc(config->var, (config->var_count + 1) * sizeof(ConfigVar)); + if (!config->var) { + free(var->name); + free(var->value); + free(var); + fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Failed to " + "allocate memory for var\n"); + return false; + } + + config->var[config->var_count] = var; + config->var_count++; + } else if (strncmp(key, "env", 3) == 0) { char env_type[256], env_value[256]; @@ -3086,6 +3125,82 @@ bool parse_option(Config *config, char *key, char *value, int line_number) { return true; } +// Expand {name} references to previously defined var values. +// Undefined references are left untouched. +char *expand_config_vars(Config *config, const char *value) { + size_t cap = strlen(value) + 1; + char *out = malloc(cap); + if (!out) + return strdup(value); + size_t olen = 0; + + const char *p = value; + while (*p) { + if (*p != '{') { + if (olen + 2 > cap) { + cap = cap * 2 + 16; + out = realloc(out, cap); + } + out[olen++] = *p++; + continue; + } + + const char *end = strchr(p, '}'); + if (!end) { + while (*p) { + if (olen + 2 > cap) { + cap = cap * 2 + 16; + out = realloc(out, cap); + } + out[olen++] = *p++; + } + break; + } + + char name[256]; + size_t nlen = end - (p + 1); + if (nlen >= sizeof(name)) { + memcpy(out + olen, p, end - p + 1); + olen += end - p + 1; + p = end + 1; + continue; + } + memcpy(name, p + 1, nlen); + name[nlen] = '\0'; + + const char *replacement = NULL; + for (int32_t i = 0; i < config->var_count; i++) { + if (config->var[i]->name && + strcmp(config->var[i]->name, name) == 0) { + replacement = config->var[i]->value; + break; + } + } + + if (replacement) { + size_t rlen = strlen(replacement); + if (olen + rlen + 1 > cap) { + while (olen + rlen + 1 > cap) + cap = cap * 2 + 16; + out = realloc(out, cap); + } + memcpy(out + olen, replacement, rlen); + olen += rlen; + } else { + // Unresolved: keep the literal {name} + if (olen + (end - p) + 2 > cap) { + cap = cap * 2 + 16; + out = realloc(out, cap); + } + memcpy(out + olen, p, end - p + 1); + olen += end - p + 1; + } + p = end + 1; + } + out[olen] = '\0'; + return out; +} + bool parse_config_line(Config *config, const char *line, int line_number) { char key[256], value[256]; if (sscanf(line, "%255[^=]=%255[^\n]", key, value) != 2) { @@ -3099,7 +3214,10 @@ bool parse_config_line(Config *config, const char *line, int line_number) { trim_whitespace(key); trim_whitespace(value); - return parse_option(config, key, value, line_number); + char *expanded = expand_config_vars(config, value); + bool ok = parse_option(config, key, expanded, line_number); + free(expanded); + return ok; } bool parse_config_file(Config *config, const char *file_path, bool must_exist) { @@ -3557,6 +3675,22 @@ void free_config(void) { config.env_count = 0; } + // 释放 var + if (config.var) { + for (int32_t i = 0; i < config.var_count; i++) { + if (config.var[i]->name) { + free((void *)config.var[i]->name); + } + if (config.var[i]->value) { + free((void *)config.var[i]->value); + } + free(config.var[i]); + } + free(config.var); + config.var = NULL; + config.var_count = 0; + } + // 释放 exec if (config.exec) { for (i = 0; i < config.exec_count; i++) { From 98e79caa4dbae042f6a1a3ede291011f66ffe80d Mon Sep 17 00:00:00 2001 From: HAZARD Date: Mon, 3 Aug 2026 00:17:45 -0300 Subject: [PATCH 2/2] docs(config): add configuration variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - assets/config.conf: added variable definitions (var=term, var=menu) and bound them using {menu} and {term} - docs/configuration/basics.md: introduced a “Configuration Variables” section with usage examples and notes Assisted-by: DeepSeek:deepseek-v4-flash --- assets/config.conf | 5 +++++ docs/configuration/basics.md | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/assets/config.conf b/assets/config.conf index 09707dcfb..fdcaa93d6 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -159,6 +159,11 @@ tagrule=id:9,layout_name:tile bind=SUPER,r,reload_config # menu and terminal +# use variables to reuse a command across multiple keybindings +# var=term,foot +# var=menu,rofi -show drun +# bind=Alt,space,spawn,{menu} +# bind=Alt,Return,spawn,{term} bind=Alt,space,spawn,rofi -show drun bind=Alt,Return,spawn,foot diff --git a/docs/configuration/basics.md b/docs/configuration/basics.md index 7afa343b9..8bac41dbf 100644 --- a/docs/configuration/basics.md +++ b/docs/configuration/basics.md @@ -64,6 +64,27 @@ env=QT_IM_MODULES,wayland;fcitx env=XMODIFIERS,@im=fcitx ``` +## Configuration Variables + +Define reusable values once and reference them anywhere in the config with `{name}`. This is handy for terminals, browsers, and other commands you bind more than once. + +> **Note:** A variable must be **defined before** you use it, and definitions are **reset** every time you reload the configuration. Undefined `{name}` references are left as-is. + +```ini +var=terminal,ghostty +var=browser,zen-browser + +bind=SUPER,Return,spawn,{terminal} +bind=SUPER,B,spawn,{browser} +``` + +The value may itself contain commas (everything after the first comma is kept), so commands with arguments work too: + +```ini +var=run,foot --alpha 90% +bind=SUPER,Return,spawn,{run} +``` + ## Autostart mangowm can automatically run commands or scripts upon startup. There are two modes for execution: