Files
lunarwm/src/Config.c
Slendi ef96c51566 Stuff
Signed-off-by: Slendi <slendi@socopon.com>
2025-08-11 06:39:52 +03:00

485 lines
10 KiB
C

#include "Config.h"
#include "common.h"
#include "lua_helpers.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <lauxlib.h>
#include <lualib.h>
#include <wlr/types/wlr_keyboard.h>
#include <xkbcommon/xkbcommon.h>
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;
}