#include "config.h" #include #include #include #include #include #include #include #include #include "toml.h" static char *trim(char *s) { while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; char *end = s + strlen(s); while (end > s && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' || end[-1] == '\r')) end--; *end = '\0'; return s; } static void config_ensure_capacity(config_t *cfg) { if (cfg->count < cfg->capacity) return; int newcap = cfg->capacity ? cfg->capacity * 2 : 16; binding_t *nb = realloc(cfg->items, newcap * sizeof(binding_t)); if (!nb) return; cfg->items = nb; cfg->capacity = newcap; } static void config_set_binding(config_t *cfg, uint32_t mods, xkb_keysym_t keysym, action_t action) { for (int i = 0; i < cfg->count; i++) { if (cfg->items[i].mods == mods && cfg->items[i].keysym == keysym) { cfg->items[i].action = action; return; } } config_ensure_capacity(cfg); if (cfg->count >= cfg->capacity) return; cfg->items[cfg->count].mods = mods; cfg->items[cfg->count].keysym = keysym; cfg->items[cfg->count].action = action; cfg->count++; } int config_parse_mods(const char *s, uint32_t *out_mods, char **out_key) { if (!s || !*s || !out_mods || !out_key) return -1; char *copy = strdup(s); if (!copy) return -1; /* count tokens */ int n = 0; /* first pass count */ char *tmp = strdup(s); char *save = NULL; char *t = strtok_r(tmp, "+", &save); /* collect */ char **toks = NULL; int cap = 0; while (t) { char *tt = trim(t); if (*tt) { char *alias = tt; if (strcmp(tt, "+") == 0) alias = "plus"; else if (strcmp(tt, "-") == 0) alias = "minus"; if (n == cap) { cap = cap ? cap * 2 : 4; toks = realloc(toks, cap * sizeof(char *)); } toks[n++] = strdup(alias); } t = strtok_r(NULL, "+", &save); } free(tmp); if (n == 0) { free(copy); free(toks); return -1; } uint32_t mods = 0; for (int i = 0; i < n - 1; i++) { if (strcasecmp(toks[i], "ctrl") == 0 || strcasecmp(toks[i], "control") == 0) mods |= MOD_CTRL; else if (strcasecmp(toks[i], "shift") == 0) mods |= MOD_SHIFT; else if (strcasecmp(toks[i], "alt") == 0) mods |= MOD_ALT; else if (strcasecmp(toks[i], "super") == 0 || strcasecmp(toks[i], "meta") == 0 || strcasecmp(toks[i], "win") == 0) mods |= MOD_SUPER; else { /* unknown modifier -> fail */ for (int j = 0; j < n; j++) free(toks[j]); free(toks); free(copy); return -1; } free(toks[i]); } *out_mods = mods; *out_key = toks[n - 1]; /* free remaining token strings except key (transferred) */ for (int i = 0; i < n - 1; i++) { /* already freed */ } free(toks); free(copy); return 0; } action_t config_parse_action(const char *s) { if (!s) return ACTION_NONE; char buf[128]; snprintf(buf, sizeof(buf), "%s", s); trim(buf); if (strcmp(buf, "quit") == 0) return ACTION_QUIT; if (strcmp(buf, "cancel") == 0) return ACTION_CANCEL; if (strcmp(buf, "rotate") == 0) return ACTION_ROTATE; if (strcmp(buf, "clear") == 0) return ACTION_CLEAR; if (strcmp(buf, "pause") == 0) return ACTION_PAUSE; if (strcmp(buf, "step") == 0) return ACTION_STEP; if (strcmp(buf, "speed_up") == 0) return ACTION_SPEED_UP; if (strcmp(buf, "speed_down") == 0) return ACTION_SPEED_DOWN; if (strcmp(buf, "toggle_grid") == 0) return ACTION_TOGGLE_GRID; if (strncmp(buf, "select:", 7) == 0) { const char *pat = buf + 7; if (strcmp(pat, "glider") == 0) return ACTION_SELECT_GLIDER; if (strcmp(pat, "blinker") == 0) return ACTION_SELECT_BLINKER; if (strcmp(pat, "block") == 0) return ACTION_SELECT_BLOCK; if (strcmp(pat, "toad") == 0) return ACTION_SELECT_TOAD; if (strcmp(pat, "beacon") == 0) return ACTION_SELECT_BEACON; if (strcmp(pat, "boat") == 0) return ACTION_SELECT_BOAT; if (strcmp(pat, "tub") == 0) return ACTION_SELECT_TUB; if (strcmp(pat, "beehive") == 0) return ACTION_SELECT_BEEHIVE; if (strcmp(pat, "rpentomino") == 0) return ACTION_SELECT_RPENTOMINO; if (strcmp(pat, "acorn") == 0) return ACTION_SELECT_ACORN; } return ACTION_NONE; } config_t config_defaults(void) { config_t cfg = {0}; struct { const char *key; action_t action; } defs[] = { {"1", ACTION_SELECT_GLIDER}, {"2", ACTION_SELECT_BLINKER}, {"3", ACTION_SELECT_BLOCK}, {"4", ACTION_SELECT_TOAD}, {"5", ACTION_SELECT_BEACON}, {"6", ACTION_SELECT_BOAT}, {"7", ACTION_SELECT_TUB}, {"8", ACTION_SELECT_BEEHIVE}, {"9", ACTION_SELECT_RPENTOMINO}, {"0", ACTION_SELECT_ACORN}, {"space", ACTION_ROTATE}, {"escape", ACTION_CANCEL}, {"ctrl+q", ACTION_QUIT}, {"p", ACTION_PAUSE}, {"n", ACTION_STEP}, {"+", ACTION_SPEED_UP}, {"-", ACTION_SPEED_DOWN}, }; int ndef = sizeof(defs) / sizeof(defs[0]); for (int i = 0; i < ndef; i++) { uint32_t mods = 0; char *key = NULL; if (config_parse_mods(defs[i].key, &mods, &key) != 0) continue; xkb_keysym_t sym = xkb_keysym_from_name(key, XKB_KEYSYM_CASE_INSENSITIVE); free(key); if (sym == XKB_KEY_NoSymbol) continue; config_set_binding(&cfg, mods, sym, defs[i].action); } return cfg; } static const char *default_file_content = "# wlgol key bindings\n" "[bindings]\n" "\"1\" = \"select:glider\"\n" "\"2\" = \"select:blinker\"\n" "\"3\" = \"select:block\"\n" "\"4\" = \"select:toad\"\n" "\"5\" = \"select:beacon\"\n" "\"6\" = \"select:boat\"\n" "\"7\" = \"select:tub\"\n" "\"8\" = \"select:beehive\"\n" "\"9\" = \"select:rpentomino\"\n" "\"0\" = \"select:acorn\"\n" "\"space\" = \"rotate\"\n" "\"escape\" = \"cancel\"\n" "\"ctrl+q\" = \"quit\"\n" "\"p\" = \"pause\"\n" "\"n\" = \"step\"\n" "\"+\" = \"speed_up\"\n" "\"-\" = \"speed_down\"\n"; int config_ensure_default_file(const char *path) { if (!path) return -1; struct stat st; if (stat(path, &st) == 0) return 0; char *parent = strdup(path); char *grand = strdup(path); dirname(parent); /* parent of parent */ char *p2 = strdup(parent); dirname(p2); mkdir(p2, 0755); mkdir(parent, 0755); free(p2); FILE *fp = fopen(path, "w"); if (!fp) { free(parent); free(grand); return -1; } fputs(default_file_content, fp); fclose(fp); free(parent); free(grand); return 0; } config_t config_load(const char *path) { config_t cfg = config_defaults(); config_ensure_default_file(path); FILE *fp = fopen(path, "r"); if (!fp) return cfg; char errbuf[512]; toml_table_t *tab = toml_parse_file(fp, errbuf, sizeof(errbuf)); fclose(fp); if (!tab) return cfg; toml_table_t *bindings = toml_table_in(tab, "bindings"); if (bindings) { int n = toml_table_nkval(bindings); for (int i = 0; i < n; i++) { const char *keystr = toml_key_in(bindings, i); if (!keystr) break; toml_datum_t d = toml_string_in(bindings, keystr); if (!d.ok) { if (d.u.s) free(d.u.s); continue; } uint32_t mods = 0; char *key = NULL; if (config_parse_mods(keystr, &mods, &key) != 0) { free(d.u.s); continue; } xkb_keysym_t sym = xkb_keysym_from_name(key, XKB_KEYSYM_CASE_INSENSITIVE); free(key); if (sym == XKB_KEY_NoSymbol) { free(d.u.s); continue; } action_t act = config_parse_action(d.u.s); free(d.u.s); if (act == ACTION_NONE) continue; config_set_binding(&cfg, mods, sym, act); } } toml_free(tab); return cfg; } void config_free(config_t *cfg) { if (!cfg) return; free(cfg->items); cfg->items = NULL; cfg->count = 0; cfg->capacity = 0; } action_t config_lookup(const config_t *cfg, uint32_t mods, xkb_keysym_t keysym) { if (!cfg) return ACTION_NONE; for (int i = 0; i < cfg->count; i++) { if (cfg->items[i].mods == mods && cfg->items[i].keysym == keysym) { return cfg->items[i].action; } } return ACTION_NONE; }