Files
wlgol/config.c
T

258 lines
8.5 KiB
C
Raw Normal View History

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <libgen.h>
#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;
char *p = copy;
/* 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) {
2026-11-08 15:47:46 +01:00
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 *));
}
2026-11-08 15:47:46 +01:00
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},
2026-11-08 15:47:46 +01:00
{"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"
2026-11-08 15:47:46 +01:00
"\"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;
}