diff --git a/CMakeLists.txt b/CMakeLists.txt index 4901fe9..0648972 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ pkg_check_modules(GLES2 REQUIRED IMPORTED_TARGET glesv2) pkg_check_modules(WLROOTS REQUIRED IMPORTED_TARGET wlroots-0.20) pkg_check_modules(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) pkg_check_modules(OPENXR REQUIRED IMPORTED_TARGET openxr) +pkg_check_modules(LUA REQUIRED IMPORTED_TARGET lua) find_program(WAYLAND_SCANNER_EXECUTABLE wayland-scanner REQUIRED) message(STATUS "Found wayland-scanner at ${WAYLAND_SCANNER_EXECUTABLE}") @@ -50,6 +51,7 @@ add_executable(${PROJECT_NAME}) target_sources(${PROJECT_NAME} PUBLIC src/vec.c + src/Config.c src/LunarWM.c src/main.c ) @@ -60,6 +62,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC PkgConfig::GLES2 PkgConfig::WLROOTS PkgConfig::OPENXR + PkgConfig::LUA raylib ) diff --git a/flake.nix b/flake.nix index 9422fd4..a194d5d 100644 --- a/flake.nix +++ b/flake.nix @@ -34,6 +34,8 @@ (pkgs.llvmPackages_20.clang-tools.override { enableLibcxx = true; }) lldb + lua + # For wlroots libxkbcommon libxkbcommon.dev diff --git a/lunarwm/init.lua b/lunarwm/init.lua new file mode 100644 index 0000000..b8da509 --- /dev/null +++ b/lunarwm/init.lua @@ -0,0 +1,15 @@ +function main(kbd) + return "Super-" .. kbd +end + +return { + input = { + keyboard = { + xkb_options = { "altwin:swap_lalt_lwin" }, + }, + }, + keybindings = { + { bind = main("Escape"), action = lunar.quit_compositor }, + { bind = main("Shift-R"), action = lunar.reload_config }, + }, +} diff --git a/lunarwm/lunarwm.dcfg b/lunarwm/lunarwm.dcfg deleted file mode 100644 index c77b06b..0000000 --- a/lunarwm/lunarwm.dcfg +++ /dev/null @@ -1,17 +0,0 @@ -fn lib = { - input = { - keyboard = { - xkb_options = [ "altwin:swap_lalt_lwin" ] - } - } - - modifier_aliases = { - Main = "Super" - } - - keybindings = [ - { bind = "Main-Escape" action = (lib.quit_compositor) } - { bind = "Main-Shift-R" action = (lib.reload_config) } - ] -} - diff --git a/src/Config.c b/src/Config.c new file mode 100644 index 0000000..8acc213 --- /dev/null +++ b/src/Config.c @@ -0,0 +1,334 @@ +#include "Config.h" + +#include +#include +#include +#include + +#include +#include + +#include + +char const *get_config_path(void) +{ + char const *paths[] = { + "lunarwm/init.lua", + }; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); 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; + + uint32_t mods = 0; + char *save = NULL; + char *tok = strtok_r(buf, "-", &save); + char *last = tok; + while (tok) { + last = tok; + tok = strtok_r(NULL, "-", &save); + } + /* walk again to accumulate modifiers (all but last) */ + strncpy(buf, bind, sizeof buf - 1); + buf[sizeof buf - 1] = 0; + save = NULL; + for (char *t = strtok_r(buf, "-", &save); t; + t = strtok_r(NULL, "-", &save)) { + if (t == last) + break; + mods |= mod_from_token(t); + } + + /* keysym from last token */ + 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; +} + +int config_load_ref(lua_State *L, int idx, Config *out) +{ + if (!L || !out) + return -1; + + memset(out, 0, sizeof(*out)); + + int idx_abs = lua_absindex(L, idx); + if (push_config_table_from_idx(L, idx_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 == 0) { + lua_pop(L, 2); + return 0; + } + + BindingRef *arr = calloc(n, sizeof(BindingRef)); + if (!arr) { + lua_pop(L, 2); + return luaL_error(L, "config: OOM allocating bindings"); + } + + size_t ok_count = 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); // pop bind string + + arr[ok_count].mods_mask = mods; + arr[ok_count].sym = sym; + arr[ok_count].action_ref = ref; + ok_count++; + + lua_pop(L, 1); // pop table entry + } + + lua_pop(L, 2); // pop keybindings table + config table + + if (ok_count == 0) { + free(arr); + out->bindings = NULL; + out->count = 0; + return 0; + } + + if (ok_count < n) { + BindingRef *shr = realloc(arr, ok_count * sizeof(BindingRef)); + if (shr) + arr = shr; + } + + out->bindings = arr; + out->count = ok_count; + return 0; +} + +void config_unref(lua_State *L, Config *cfg) +{ + if (!cfg || !cfg->bindings) { + if (cfg) + cfg->count = 0; + return; + } + for (size_t i = 0; i < cfg->count; i++) { + if (cfg->bindings[i].action_ref != LUA_NOREF + && cfg->bindings[i].action_ref != LUA_REFNIL) { + luaL_unref(L, LUA_REGISTRYINDEX, cfg->bindings[i].action_ref); + } + } + free(cfg->bindings); + cfg->bindings = NULL; + cfg->count = 0; +} + +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) { + if (dupstr(path, &cm->path) != 0) { + lua_close(cm->L); + free(cm); + return NULL; + } + } + + if (cm->path && load_config_file(cm->L, cm->path) == 0) { + if (config_load_ref(cm->L, -1, &cm->cfg) != 0) { + lua_pop(cm->L, 1); + } else { + lua_pop(cm->L, 1); + } + } + 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); + return rc; +} + +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; +} + +int trigger_ref_modsym( + lua_State *L, Config const *cfg, uint32_t mods, xkb_keysym_t sym) +{ + if (!L || !cfg) + return -1; + for (size_t i = 0; i < cfg->count; i++) { + BindingRef const *br = &cfg->bindings[i]; + if (br->sym != sym) + continue; + if ((mods & br->mods_mask) != br->mods_mask) + continue; // require all mods + lua_rawgeti(L, LUA_REGISTRYINDEX, br->action_ref); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return -2; + } + 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 -3; + } + return 0; + } + return 1; +} diff --git a/src/Config.h b/src/Config.h new file mode 100644 index 0000000..a33a817 --- /dev/null +++ b/src/Config.h @@ -0,0 +1,43 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include +#include + +#include + +typedef struct { + xkb_keysym_t sym; + uint32_t mods_mask; + int action_ref; // luaL_ref(L, LUA_REGISTRYINDEX) +} BindingRef; + +typedef struct { + BindingRef *bindings; + size_t count; +} Config; + +char const *get_config_path(void); + +int config_load_ref(lua_State *L, int idx, Config *out); +void config_unref(lua_State *L, Config *cfg); +int trigger_ref_modsym( + lua_State *L, Config const *cfg, uint32_t mods, xkb_keysym_t sym); + +struct ConfigManager { + lua_State *L; + Config cfg; + char *path; +}; +typedef struct ConfigManager ConfigManager; + +ConfigManager *config_manager_create(char const *path); +void config_manager_destroy(ConfigManager *cm); + +int config_manager_reload(ConfigManager *cm); + +lua_State *config_manager_lua(ConfigManager *cm); +Config const *config_manager_get(ConfigManager *cm); +char const *config_manager_path(ConfigManager *cm); + +#endif // CONFIG_H diff --git a/src/LunarWM.c b/src/LunarWM.c index 5e287db..8085b6a 100644 --- a/src/LunarWM.c +++ b/src/LunarWM.c @@ -1,6 +1,7 @@ #include "LunarWM.h" #include +#include #include #include @@ -127,6 +128,66 @@ bool LunarWM_Toplevel_update(LunarWM_Toplevel *this) return true; } +static void keysym_name(xkb_keysym_t sym, char *buf, size_t bufsz) +{ + if (bufsz == 0) + return; + buf[0] = 0; + + char tmp[128] = { 0 }; + int n = xkb_keysym_get_name(sym, tmp, sizeof tmp); + if (n <= 0) { + snprintf(buf, bufsz, "Unknown"); + return; + } + + if (strlen(tmp) == 1) { + buf[0] = (char)toupper((unsigned char)tmp[0]); + buf[1] = 0; + } else { + snprintf(buf, bufsz, "%s", tmp); + } +} + +static size_t fill_mod_list(uint32_t mods, char const *outv[4]) +{ + size_t k = 0; + if (mods & WLR_MODIFIER_LOGO) + outv[k++] = "Super"; + if (mods & WLR_MODIFIER_SHIFT) + outv[k++] = "Shift"; + if (mods & WLR_MODIFIER_CTRL) + outv[k++] = "Ctrl"; + if (mods & WLR_MODIFIER_ALT) + outv[k++] = "Alt"; + return k; +} + +static char *make_hotkey_string(uint32_t mods, xkb_keysym_t sym) +{ + char keyname[128]; + keysym_name(sym, keyname, sizeof keyname); + + char const *mods_list[4] = { 0 }; + size_t mcount = fill_mod_list(mods, mods_list); + + size_t need = strlen(keyname) + 1; + for (size_t i = 0; i < mcount; ++i) + need += strlen(mods_list[i]) + 1; + + char *s = (char *)malloc(need); + if (!s) + return NULL; + + s[0] = 0; + for (size_t i = 0; i < mcount; ++i) { + strcat(s, mods_list[i]); + strcat(s, "-"); + } + strcat(s, keyname); + return s; +} + static void Keyboard_modifiers_notify(struct wl_listener *listener, void *) { auto *kbd @@ -150,7 +211,7 @@ static void Keyboard_key_notify(struct wl_listener *listener, void *data) xkb_keysym_t const keysym = xkb_state_key_get_one_sym(kbd->wlr_keyboard->xkb_state, keycode); - bool const handled = false; + bool handled = false; uint32_t const modifiers = wlr_keyboard_get_modifiers(kbd->wlr_keyboard); if (server->wayland.session && event->state == WL_KEYBOARD_KEY_STATE_PRESSED && keysym >= XKB_KEY_XF86Switch_VT_1 @@ -160,8 +221,11 @@ static void Keyboard_key_notify(struct wl_listener *listener, void *data) return; } if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { - if ((modifiers & WLR_MODIFIER_LOGO) && syms[XKB_KEY_Q]) { - LunarWM_terminate(server); + for (int i = 0; i < nsyms; i++) { + if (trigger_ref_modsym( + server->cman->L, &server->cman->cfg, modifiers, syms[i]) + == 0) + return; // handled } } @@ -534,7 +598,7 @@ static bool init_xr(LunarWM *this) wlr_log(WLR_ERROR, "Failed to get GLES graphics requirements"); return false; } - printf("OpenGL ES range: %d.%d.%d - %d.%d.%d\n", + wlr_log(WLR_INFO, "OpenGL ES range: %d.%d.%d - %d.%d.%d\n", XR_VERSION_MAJOR(reqs.minApiVersionSupported), XR_VERSION_MINOR(reqs.minApiVersionSupported), XR_VERSION_PATCH(reqs.minApiVersionSupported), @@ -982,6 +1046,25 @@ static bool init_xr(LunarWM *this) return true; } +static int l_reload_config(lua_State *L) +{ + ConfigManager *cm = g_wm.cman; + if (!cm) { + lua_pushnil(L); + return 1; + } + + config_manager_reload(cm); + lua_pushnil(L); + return 1; +} + +static int l_quit_compositor(lua_State *L) +{ + LunarWM_terminate(&g_wm); + return 0; +} + bool LunarWM_init(LunarWM *this) { { // Init defaults @@ -999,6 +1082,22 @@ bool LunarWM_init(LunarWM *this) this->renderer.camera.projection = CAMERA_PERSPECTIVE; } + this->cman = config_manager_create(get_config_path()); + assert(this->cman); + + { + lua_State *L = this->cman->L; + + lua_newtable(L); + lua_pushcfunction(L, l_quit_compositor); + lua_setfield(L, -2, "quit_compositor"); + lua_pushcfunction(L, l_reload_config); + lua_setfield(L, -2, "reload_config"); + lua_setglobal(L, "lunar"); + + config_manager_reload(this->cman); + } + if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr) { wlr_log(WLR_ERROR, "This compositor can only be ran in DRM mode."); return false; diff --git a/src/LunarWM.h b/src/LunarWM.h index 7473d5a..afe4a99 100644 --- a/src/LunarWM.h +++ b/src/LunarWM.h @@ -31,6 +31,7 @@ #include #include +#include "Config.h" #include "common.h" struct LunarWM; @@ -158,6 +159,9 @@ typedef struct LunarWM { Camera3D camera; } renderer; + Config config; + ConfigManager *cman; + bool initialized; bool running; } LunarWM;