Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions assets/config.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 21 additions & 0 deletions docs/configuration/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
136 changes: 135 additions & 1 deletion src/config/parse_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ typedef struct {
char *value;
} ConfigEnv;

typedef struct {
char *name;
char *value;
} ConfigVar;

typedef struct {
const char *id;
const char *title;
Expand Down Expand Up @@ -391,6 +396,9 @@ typedef struct {
ConfigEnv **env;
int32_t env_count;

ConfigVar **var;
int32_t var_count;

char **exec;
int32_t exec_count;

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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++) {
Expand Down