#include "Config.h" #include "common.h" #include "lua_helpers.h" #include #include #include #include #include #include #include #include char const *get_config_path(void) { static char const *paths[] = { "lunarwm/init.lua", }; for (size_t i = 0; i < ARRAY_SZ(paths); ++i) { char const *p = paths[i]; struct stat s; if (stat(p, &s) == 0) return p; } return NULL; } static int dupstr(char const *s, char **out) { if (!s) { *out = NULL; return 0; } size_t n = strlen(s) + 1; char *m = (char *)malloc(n); if (!m) return -1; memcpy(m, s, n); *out = m; return 0; } static uint32_t mod_from_token(char const *t) { if (!t) return 0; if (strcasecmp(t, "Super") == 0) return WLR_MODIFIER_LOGO; if (strcasecmp(t, "Shift") == 0) return WLR_MODIFIER_SHIFT; if (strcasecmp(t, "Ctrl") == 0 || strcasecmp(t, "Control") == 0) return WLR_MODIFIER_CTRL; if (strcasecmp(t, "Alt") == 0) return WLR_MODIFIER_ALT; return 0; } static int parse_bind( char const *bind, uint32_t *mods_out, xkb_keysym_t *sym_out) { if (!bind || !mods_out || !sym_out) return -1; char buf[256]; strncpy(buf, bind, sizeof buf - 1); buf[sizeof buf - 1] = 0; char *save = NULL; char *tok = strtok_r(buf, "-", &save); char *last = tok; while (tok) { last = tok; tok = strtok_r(NULL, "-", &save); } strncpy(buf, bind, sizeof buf - 1); buf[sizeof buf - 1] = 0; save = NULL; uint32_t mods = 0; for (char *t = strtok_r(buf, "-", &save); t; t = strtok_r(NULL, "-", &save)) { if (t == last) break; mods |= mod_from_token(t); } int flags = XKB_KEYSYM_CASE_INSENSITIVE; xkb_keysym_t sym = xkb_keysym_from_name(last, flags); if (sym == XKB_KEY_NoSymbol) return -1; *mods_out = mods; *sym_out = sym; return 0; } static int push_config_table_from_idx(lua_State *L, int idx_abs) { if (!lua_istable(L, idx_abs)) return luaL_error(L, "config: expected table at index %d", idx_abs); lua_pushvalue(L, idx_abs); return 0; } static int join_string_array(lua_State *L, int idx_abs, char **out) { *out = NULL; if (!lua_istable(L, idx_abs)) return 0; size_t n = (size_t)lua_rawlen(L, idx_abs); if (n == 0) return 0; size_t total = 1; for (size_t i = 1; i <= n; ++i) { lua_rawgeti(L, idx_abs, (lua_Integer)i); size_t len = 0; (void)lua_tolstring(L, -1, &len); total += len + (i < n ? 1 : 0); lua_pop(L, 1); } char *buf = (char *)malloc(total); if (!buf) return -1; size_t off = 0; for (size_t i = 1; i <= n; ++i) { lua_rawgeti(L, idx_abs, (lua_Integer)i); size_t len = 0; char const *s = lua_tolstring(L, -1, &len); if (!s) { lua_pop(L, 1); free(buf); return -1; } memcpy(buf + off, s, len); off += len; lua_pop(L, 1); if (i != n) buf[off++] = ','; } buf[off] = 0; *out = buf; return 0; } int config_load_ref(lua_State *L, int idx, Config *out) { if (!L || !out) return -1; memset(out, 0, sizeof(*out)); // ======== DEFAULTS ======== out->space.offset = (Vector3) { 0, 0, 0 }; out->space.initial_center = (Vector3) { 0, 1, 0 }; out->space.radius = 1.0f; out->space.window_scale = 0.001f; out->displays.hud.size = 720; out->displays.hud.font_size = 24; out->displays.virtual.resolution = (Vector2) { 2560, 1440 }; // ====== END DEFAULTS ====== out->xwayland.enabled = true; out->xwayland.lazy = true; int cfg_abs = lua_absindex(L, idx); if (push_config_table_from_idx(L, cfg_abs) != 0) return -1; lua_getfield(L, -1, "keybindings"); if (!lua_istable(L, -1)) { lua_pop(L, 2); return luaL_error(L, "config: 'keybindings' must be a table (array)"); } size_t n = (size_t)lua_rawlen(L, -1); if (n) { BindingRef *arr = (BindingRef *)calloc(n, sizeof(*arr)); if (!arr) { lua_pop(L, 2); return luaL_error(L, "config: OOM"); } size_t ok = 0; for (size_t i = 0; i < n; ++i) { lua_rawgeti(L, -1, (lua_Integer)(i + 1)); if (!lua_istable(L, -1)) { lua_pop(L, 1); continue; } lua_getfield(L, -1, "bind"); char const *bind = lua_tostring(L, -1); if (!bind) { lua_pop(L, 2); continue; } uint32_t mods = 0; xkb_keysym_t sym = XKB_KEY_NoSymbol; if (parse_bind(bind, &mods, &sym) != 0) { lua_pop(L, 2); continue; } lua_getfield(L, -2, "action"); if (!lua_isfunction(L, -1)) { lua_pop(L, 3); continue; } int ref = luaL_ref(L, LUA_REGISTRYINDEX); lua_pop(L, 1); arr[ok].mods_mask = mods; arr[ok].sym = sym; arr[ok].action_ref = ref; ++ok; lua_pop(L, 1); } if (ok == 0) { free(arr); } else if (ok < n) { BindingRef *shr = (BindingRef *)realloc(arr, ok * sizeof(*shr)); if (shr) arr = shr; } out->keybindings.items = ok ? arr : NULL; out->keybindings.count = ok; } lua_pop(L, 1); lua_getfield(L, -1, "input"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "keyboard"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "xkb_options"); if (lua_istable(L, -1)) { (void)join_string_array( L, lua_absindex(L, -1), &out->input.keyboard.xkb_options); } lua_pop(L, 1); } lua_pop(L, 1); } lua_pop(L, 1); lua_getfield(L, -1, "space"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "offset"); if (lua_istable(L, -1) || lua_isuserdata(L, -1)) out->space.offset = lua_readVector3(L, lua_absindex(L, -1)); lua_pop(L, 1); lua_getfield(L, -1, "initial_center"); if (lua_istable(L, -1) || lua_isuserdata(L, -1)) out->space.initial_center = lua_readVector3(L, lua_absindex(L, -1)); lua_pop(L, 1); lua_getfield(L, -1, "radius"); if (lua_isnumber(L, -1)) out->space.radius = (float)lua_tonumber(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "window_scale"); if (lua_isnumber(L, -1)) out->space.window_scale = (float)lua_tonumber(L, -1); lua_pop(L, 1); } lua_pop(L, 1); lua_getfield(L, -1, "xwayland"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "enabled"); if (lua_isboolean(L, -1)) out->xwayland.enabled = lua_toboolean(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "lazy"); if (lua_isboolean(L, -1)) out->xwayland.lazy = lua_toboolean(L, -1); lua_pop(L, 1); } lua_pop(L, 1); lua_getfield(L, -1, "displays"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "hud"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "size"); if (lua_isnumber(L, -1)) out->displays.hud.size = (float)lua_tonumber(L, -1); lua_pop(L, 1); lua_getfield(L, -1, "font_size"); if (lua_isnumber(L, -1)) out->displays.hud.font_size = (float)lua_tonumber(L, -1); lua_pop(L, 1); } lua_pop(L, 1); lua_getfield(L, -1, "virtual"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "resolution"); if (lua_istable(L, -1) || lua_isuserdata(L, -1)) out->displays.virtual.resolution = lua_readVector2(L, lua_absindex(L, -1)); lua_pop(L, 1); } lua_pop(L, 1); } lua_pop(L, 1); lua_getfield(L, -1, "cubemap"); if (lua_isstring(L, -1)) { char const *s = lua_tostring(L, -1); if (s && s[0]) { (void)dupstr(s, (char **)&out->cubemap); } } lua_pop(L, 1); lua_pop(L, 1); return 0; } void config_unref(lua_State *L, Config *cfg) { if (!cfg) return; for (size_t i = 0; i < cfg->keybindings.count; ++i) { int r = cfg->keybindings.items ? cfg->keybindings.items[i].action_ref : LUA_NOREF; if (r != LUA_NOREF && r != LUA_REFNIL) luaL_unref(L, LUA_REGISTRYINDEX, r); } free(cfg->keybindings.items); cfg->keybindings.items = NULL; cfg->keybindings.count = 0; free(cfg->input.keyboard.xkb_options); cfg->input.keyboard.xkb_options = NULL; if (cfg->cubemap) { free((void *)cfg->cubemap); cfg->cubemap = NULL; } } void config_trigger_ref(lua_State *L, Config *cfg, int ref) { lua_rawgeti(L, LUA_REGISTRYINDEX, ref); if (!lua_isfunction(L, -1)) { lua_pop(L, 1); return; } if (lua_pcall(L, 0, 0, 0) != LUA_OK) { fprintf(stderr, "config: action error: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); return; } } static int load_config_file(lua_State *L, char const *path) { if (!path || !path[0]) return -1; if (luaL_loadfile(L, path) != LUA_OK) { char const *err = lua_tostring(L, -1); fprintf( stderr, "config: loadfile failed: %s\n", err ? err : "(unknown)"); lua_pop(L, 1); return -1; } if (lua_pcall(L, 0, 1, 0) != LUA_OK) { char const *err = lua_tostring(L, -1); fprintf(stderr, "config: executing '%s' failed: %s\n", path, err ? err : "(unknown)"); lua_pop(L, 1); return -1; } if (!lua_istable(L, -1)) { lua_pop(L, 1); fprintf(stderr, "config: '%s' did not return a table\n", path); return -1; } return 0; } ConfigManager *config_manager_create(char const *path) { ConfigManager *cm = (ConfigManager *)calloc(1, sizeof(*cm)); if (!cm) return NULL; cm->L = luaL_newstate(); if (!cm->L) { free(cm); return NULL; } luaL_openlibs(cm->L); if (!path) path = get_config_path(); if (path && dupstr(path, &cm->path) != 0) { lua_close(cm->L); free(cm); return NULL; } return cm; } void config_manager_destroy(ConfigManager *cm) { if (!cm) return; config_unref(cm->L, &cm->cfg); if (cm->L) lua_close(cm->L); free(cm->path); free(cm); } int config_manager_reload(ConfigManager *cm) { if (!cm || !cm->path) return -1; config_unref(cm->L, &cm->cfg); if (load_config_file(cm->L, cm->path) != 0) return -1; int rc = config_load_ref(cm->L, -1, &cm->cfg); lua_pop(cm->L, 1); if (rc != 0) return rc; if (cm->cfg.cubemap && cm->cfg.cubemap[0] != '/') { char const *slash = strrchr(cm->path, '/'); char const *dir = "."; size_t dirlen = 1; if (slash) { dir = cm->path; dirlen = (size_t)(slash - cm->path); } size_t n = dirlen + 1 + strlen(cm->cfg.cubemap) + 1; // dir + '/' + hdri + '\0' char *full = (char *)malloc(n); if (full) { memcpy(full, dir, dirlen); full[dirlen] = '/'; strcpy(full + dirlen + 1, cm->cfg.cubemap); free((void *)cm->cfg.cubemap); cm->cfg.cubemap = full; } } return 0; } lua_State *config_manager_lua(ConfigManager *cm) { return cm ? cm->L : NULL; } Config const *config_manager_get(ConfigManager *cm) { return cm ? &cm->cfg : NULL; } char const *config_manager_path(ConfigManager *cm) { return cm ? cm->path : NULL; }