summaryrefslogtreecommitdiff
path: root/src/config/lua/bindings/LuaBindingsInternal.cpp
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2026-04-26 15:16:36 +0100
committerGitHub <noreply@github.com>2026-04-26 15:16:36 +0100
commit5ba33f846129f3e6c0505b19f94634b24505e828 (patch)
treeaaacabe678006040c342935481a86607c66607fd /src/config/lua/bindings/LuaBindingsInternal.cpp
parent329b2b356dd92ac07f4e2aa69cbce82ee9ba9d19 (diff)
config/lua: init lua config manager, use lua if available (#13817)
Adds a new lua config manager, which will automatically be used if there is a lua config present.
Diffstat (limited to 'src/config/lua/bindings/LuaBindingsInternal.cpp')
-rw-r--r--src/config/lua/bindings/LuaBindingsInternal.cpp502
1 files changed, 502 insertions, 0 deletions
diff --git a/src/config/lua/bindings/LuaBindingsInternal.cpp b/src/config/lua/bindings/LuaBindingsInternal.cpp
new file mode 100644
index 00000000..61a90823
--- /dev/null
+++ b/src/config/lua/bindings/LuaBindingsInternal.cpp
@@ -0,0 +1,502 @@
+#include "LuaBindingsInternal.hpp"
+
+using namespace Config;
+using namespace Config::Lua;
+using namespace Config::Lua::Bindings;
+
+namespace CA = Config::Actions;
+
+namespace {
+ constexpr const char* LUA_WINDOW_MT = "HL.Window";
+ constexpr const char* LUA_WORKSPACE_MT = "HL.Workspace";
+ constexpr const char* LUA_MONITOR_MT = "HL.Monitor";
+}
+
+std::string Internal::argStr(lua_State* L, int idx) {
+ if (lua_type(L, idx) == LUA_TNUMBER)
+ return std::to_string((long long)lua_tonumber(L, idx));
+
+ size_t n = 0;
+ const char* s = luaL_checklstring(L, idx, &n);
+ return {s, n};
+}
+
+std::optional<std::string> Internal::tableOptStr(lua_State* L, int idx, const char* field) {
+ lua_getfield(L, idx, field);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return std::nullopt;
+ }
+
+ const char* s = lua_tostring(L, -1);
+ lua_pop(L, 1);
+ return s ? std::optional(std::string(s)) : std::nullopt;
+}
+
+std::optional<double> Internal::tableOptNum(lua_State* L, int idx, const char* field) {
+ lua_getfield(L, idx, field);
+ if (lua_isnil(L, -1) || !lua_isnumber(L, -1)) {
+ lua_pop(L, 1);
+ return std::nullopt;
+ }
+
+ const double v = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+ return v;
+}
+
+std::optional<bool> Internal::tableOptBool(lua_State* L, int idx, const char* field) {
+ lua_getfield(L, idx, field);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return std::nullopt;
+ }
+
+ const bool v = lua_toboolean(L, -1);
+ lua_pop(L, 1);
+ return v;
+}
+
+PHLMONITOR Internal::monitorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
+ idx = lua_absindex(L, idx);
+
+ if (lua_isnil(L, idx))
+ return nullptr;
+
+ if (auto* ref = sc<PHLMONITORREF*>(luaL_testudata(L, idx, LUA_MONITOR_MT)); ref)
+ return ref->lock();
+
+ if (lua_isstring(L, idx) || lua_isnumber(L, idx))
+ return g_pCompositor->getMonitorFromString(argStr(L, idx));
+
+ Internal::configError(L, "{}: expected a monitor object or selector", fnName);
+ return nullptr;
+}
+
+PHLWORKSPACE Internal::workspaceFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
+ idx = lua_absindex(L, idx);
+
+ if (lua_isnil(L, idx))
+ return nullptr;
+
+ if (auto* ref = sc<PHLWORKSPACEREF*>(luaL_testudata(L, idx, LUA_WORKSPACE_MT)); ref) {
+ auto ws = ref->lock();
+ if (!ws || ws->inert())
+ return nullptr;
+
+ return ws;
+ }
+
+ if (lua_isstring(L, idx) || lua_isnumber(L, idx))
+ return g_pCompositor->getWorkspaceByString(argStr(L, idx));
+
+ Internal::configError(L, "{}: expected a workspace object or selector", fnName);
+ return nullptr;
+}
+
+PHLWINDOW Internal::windowFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
+ idx = lua_absindex(L, idx);
+
+ if (lua_isnil(L, idx))
+ return nullptr;
+
+ if (auto* ref = sc<PHLWINDOWREF*>(luaL_testudata(L, idx, LUA_WINDOW_MT)); ref)
+ return ref->lock();
+
+ if (lua_isstring(L, idx) || lua_isnumber(L, idx))
+ return g_pCompositor->getWindowByRegex(argStr(L, idx));
+
+ Internal::configError(L, "{}: expected a window object or selector", fnName);
+ return nullptr;
+}
+
+std::optional<PHLMONITOR> Internal::tableOptMonitor(lua_State* L, int idx, const char* field, const char* fnName) {
+ idx = lua_absindex(L, idx);
+ lua_getfield(L, idx, field);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return std::nullopt;
+ }
+
+ auto mon = monitorFromLuaSelectorOrObject(L, -1, fnName);
+ lua_pop(L, 1);
+ return mon;
+}
+
+std::optional<PHLWORKSPACE> Internal::tableOptWorkspace(lua_State* L, int idx, const char* field, const char* fnName) {
+ idx = lua_absindex(L, idx);
+ lua_getfield(L, idx, field);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return std::nullopt;
+ }
+
+ auto ws = workspaceFromLuaSelectorOrObject(L, -1, fnName);
+ lua_pop(L, 1);
+ return ws;
+}
+
+std::optional<PHLWINDOW> Internal::tableOptWindow(lua_State* L, int idx, const char* field, const char* fnName) {
+ idx = lua_absindex(L, idx);
+ lua_getfield(L, idx, field);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return std::nullopt;
+ }
+
+ auto window = windowFromLuaSelectorOrObject(L, -1, fnName);
+ lua_pop(L, 1);
+ return window;
+}
+
+std::optional<std::string> Internal::monitorSelectorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
+ idx = lua_absindex(L, idx);
+
+ if (lua_isnil(L, idx))
+ return std::nullopt;
+
+ if (auto* ref = sc<PHLMONITORREF*>(luaL_testudata(L, idx, LUA_MONITOR_MT)); ref) {
+ const auto mon = ref->lock();
+ if (!mon) {
+ Internal::configError(L, "{}: monitor object is expired", fnName);
+ return std::nullopt;
+ }
+
+ return mon->m_name;
+ }
+
+ if (lua_isstring(L, idx) || lua_isnumber(L, idx))
+ return argStr(L, idx);
+
+ Internal::configError(L, "{}: expected a monitor object or selector", fnName);
+ return std::nullopt;
+}
+
+std::optional<std::string> Internal::workspaceSelectorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
+ idx = lua_absindex(L, idx);
+
+ if (lua_isnil(L, idx))
+ return std::nullopt;
+
+ if (auto* ref = sc<PHLWORKSPACEREF*>(luaL_testudata(L, idx, LUA_WORKSPACE_MT)); ref) {
+ const auto ws = ref->lock();
+ if (!ws || ws->inert()) {
+ Internal::configError(L, "{}: workspace object is expired", fnName);
+ return std::nullopt;
+ }
+
+ return std::to_string(ws->m_id);
+ }
+
+ if (lua_isstring(L, idx) || lua_isnumber(L, idx))
+ return argStr(L, idx);
+
+ Internal::configError(L, "{}: expected a workspace object or selector", fnName);
+ return std::nullopt;
+}
+
+std::optional<std::string> Internal::windowSelectorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
+ idx = lua_absindex(L, idx);
+
+ if (lua_isnil(L, idx))
+ return std::nullopt;
+
+ if (auto* ref = sc<PHLWINDOWREF*>(luaL_testudata(L, idx, LUA_WINDOW_MT)); ref) {
+ const auto w = ref->lock();
+ if (!w) {
+ Internal::configError(L, "{}: window object is expired", fnName);
+ return std::nullopt;
+ }
+
+ return std::format("0x{:x}", reinterpret_cast<uintptr_t>(w.get()));
+ }
+
+ if (lua_isstring(L, idx) || lua_isnumber(L, idx))
+ return argStr(L, idx);
+
+ Internal::configError(L, "{}: expected a window object or selector", fnName);
+ return std::nullopt;
+}
+
+std::optional<std::string> Internal::tableOptMonitorSelector(lua_State* L, int idx, const char* field, const char* fnName) {
+ idx = lua_absindex(L, idx);
+ lua_getfield(L, idx, field);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return std::nullopt;
+ }
+
+ auto selector = monitorSelectorFromLuaSelectorOrObject(L, -1, fnName);
+ lua_pop(L, 1);
+ return selector;
+}
+
+std::optional<std::string> Internal::tableOptWorkspaceSelector(lua_State* L, int idx, const char* field, const char* fnName) {
+ idx = lua_absindex(L, idx);
+ lua_getfield(L, idx, field);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return std::nullopt;
+ }
+
+ auto selector = workspaceSelectorFromLuaSelectorOrObject(L, -1, fnName);
+ lua_pop(L, 1);
+ return selector;
+}
+
+std::optional<std::string> Internal::tableOptWindowSelector(lua_State* L, int idx, const char* field, const char* fnName) {
+ idx = lua_absindex(L, idx);
+ lua_getfield(L, idx, field);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return std::nullopt;
+ }
+
+ auto selector = windowSelectorFromLuaSelectorOrObject(L, -1, fnName);
+ lua_pop(L, 1);
+ return selector;
+}
+
+std::string Internal::requireTableFieldMonitorSelector(lua_State* L, int idx, const char* field, const char* fnName) {
+ auto selector = tableOptMonitorSelector(L, idx, field, fnName);
+ if (!selector) {
+ Internal::configError(L, "{}: '{}' is required", fnName, field);
+ return "";
+ }
+
+ return *selector;
+}
+
+std::string Internal::requireTableFieldWorkspaceSelector(lua_State* L, int idx, const char* field, const char* fnName) {
+ auto selector = tableOptWorkspaceSelector(L, idx, field, fnName);
+ if (!selector) {
+ Internal::configError(L, "{}: '{}' is required", fnName, field);
+ return "";
+ }
+
+ return *selector;
+}
+
+std::string Internal::requireTableFieldWindowSelector(lua_State* L, int idx, const char* field, const char* fnName) {
+ auto selector = tableOptWindowSelector(L, idx, field, fnName);
+ if (!selector) {
+ Internal::configError(L, "{}: '{}' is required", fnName, field);
+ return "";
+ }
+
+ return *selector;
+}
+
+Math::eDirection Internal::parseDirectionStr(const std::string& str) {
+ if (str == "left" || str == "l")
+ return Math::DIRECTION_LEFT;
+ if (str == "right" || str == "r")
+ return Math::DIRECTION_RIGHT;
+ if (str == "up" || str == "u" || str == "t")
+ return Math::DIRECTION_UP;
+ if (str == "down" || str == "d" || str == "b")
+ return Math::DIRECTION_DOWN;
+ return Math::DIRECTION_DEFAULT;
+}
+
+CA::eTogglableAction Internal::parseToggleStr(const std::string& str) {
+ if (str.empty() || str == "toggle")
+ return CA::TOGGLE_ACTION_TOGGLE;
+ if (str == "enable" || str == "on")
+ return CA::TOGGLE_ACTION_ENABLE;
+ if (str == "disable" || str == "off")
+ return CA::TOGGLE_ACTION_DISABLE;
+ return CA::TOGGLE_ACTION_TOGGLE;
+}
+
+std::optional<PHLWINDOW> Internal::windowFromUpval(lua_State* L, int idx) {
+ if (lua_isnil(L, lua_upvalueindex(idx)))
+ return std::nullopt;
+
+ return g_pCompositor->getWindowByRegex(lua_tostring(L, lua_upvalueindex(idx)));
+}
+
+void Internal::pushWindowUpval(lua_State* L, int tableIdx) {
+ if (lua_istable(L, tableIdx)) {
+ auto selector = tableOptWindowSelector(L, tableIdx, "window", "window selector");
+ if (!selector)
+ lua_pushnil(L);
+ else
+ lua_pushstring(L, selector->c_str());
+ } else
+ lua_pushnil(L);
+}
+
+void Internal::checkResult(lua_State* L, const CA::ActionResult& r) {
+ if (!r)
+ Internal::configError(L, "{}", r.error());
+}
+
+PHLWORKSPACE Internal::resolveWorkspaceStr(const std::string& args) {
+ const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(args);
+ if (id == WORKSPACE_INVALID)
+ return nullptr;
+
+ auto ws = g_pCompositor->getWorkspaceByID(id);
+ if (!ws) {
+ const auto PMONITOR = Desktop::focusState()->monitor();
+ if (PMONITOR)
+ ws = g_pCompositor->createNewWorkspace(id, PMONITOR->m_id, name, false);
+ }
+
+ return ws;
+}
+
+PHLMONITOR Internal::resolveMonitorStr(const std::string& args) {
+ auto mon = g_pCompositor->getMonitorFromString(args);
+ return mon;
+}
+
+std::string Internal::getSourceInfo(lua_State* L, int stackLevel) {
+ lua_Debug ar = {};
+ std::string sourceInfo = "?:?";
+
+ if (lua_getstack(L, stackLevel, &ar) && lua_getinfo(L, "Sl", &ar)) {
+ const char* src = ar.source;
+ if (src && src[0] == '@')
+ src++;
+
+ sourceInfo = std::format("{}:{}", src ? src : "?", ar.currentline);
+ }
+
+ return sourceInfo;
+}
+
+std::string Internal::requireTableFieldStr(lua_State* L, int idx, const char* field, const char* fnName) {
+ auto value = tableOptStr(L, idx, field);
+ if (!value) {
+ Internal::configError(L, "{}: '{}' is required", fnName, field);
+ return "";
+ }
+
+ return *value;
+}
+
+double Internal::requireTableFieldNum(lua_State* L, int idx, const char* field, const char* fnName) {
+ auto value = tableOptNum(L, idx, field);
+ if (!value) {
+ Internal::configError(L, "{}: '{}' is required", fnName, field);
+ return 0;
+ }
+
+ return *value;
+}
+
+CA::eTogglableAction Internal::tableToggleAction(lua_State* L, int idx, const char* field) {
+ if (!lua_istable(L, idx))
+ return CA::TOGGLE_ACTION_TOGGLE;
+
+ if (const auto action = tableOptStr(L, idx, field); action.has_value())
+ return parseToggleStr(*action);
+
+ return CA::TOGGLE_ACTION_TOGGLE;
+}
+
+void Internal::setFn(lua_State* L, const char* name, lua_CFunction fn) {
+ lua_pushcfunction(L, fn);
+ lua_setfield(L, -2, name);
+}
+
+void Internal::setMgrFn(lua_State* L, CConfigManager* mgr, const char* name, lua_CFunction fn) {
+ lua_pushlightuserdata(L, mgr);
+ lua_pushcclosure(L, fn, 1);
+ lua_setfield(L, -2, name);
+}
+
+int Internal::configError(lua_State* L, std::string s, int stackLevel) {
+ s = std::format("{}: {}", getSourceInfo(L, stackLevel), std::move(s));
+
+ Log::logger->log(Log::ERR, "Error in lua: {}", std::string_view{s});
+ Config::Lua::mgr()->addError(std::move(s));
+ return 0;
+}
+
+std::expected<std::string, std::string> Internal::ruleValueToString(lua_State* L) {
+ if (lua_type(L, -1) == LUA_TBOOLEAN)
+ return lua_toboolean(L, -1) ? "true" : "false";
+
+ if (lua_isinteger(L, -1))
+ return std::to_string(lua_tointeger(L, -1));
+
+ if (lua_isnumber(L, -1))
+ return std::to_string(lua_tonumber(L, -1));
+
+ if (lua_isstring(L, -1))
+ return std::string(lua_tostring(L, -1));
+
+ return std::unexpected("value must be a string, bool, or number");
+}
+
+std::expected<SP<Desktop::Rule::CWindowRule>, int> Internal::buildRuleFromTable(lua_State* L, int idx) {
+ SP<Desktop::Rule::CWindowRule> rule;
+
+ if (!lua_isnoneornil(L, idx)) {
+ if (!lua_istable(L, idx))
+ return std::unexpected(Internal::configError(L, "buildRuleFromTable: failed to build table for exec rules from argument"));
+
+ int optsIdx = lua_absindex(L, idx);
+ bool hasRuleEffects = false;
+
+ rule = makeShared<Desktop::Rule::CWindowRule>();
+
+ lua_pushnil(L);
+ while (lua_next(L, optsIdx) != 0) {
+ if (lua_type(L, -2) != LUA_TSTRING) {
+ lua_pop(L, 1);
+ return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect key must be a string"));
+ }
+
+ std::string key = lua_tostring(L, -2);
+
+ if (key == "floating")
+ key = "float";
+
+ const auto* desc = Internal::findDescByName(Internal::WINDOW_RULE_EFFECT_DESCS, key);
+ if (!desc) {
+ const auto dynamicEffect = Desktop::Rule::windowEffects()->get(key);
+ if (!dynamicEffect.has_value()) {
+ lua_pop(L, 1);
+ return std::unexpected(Internal::configError(L, "buildRuleFromTable: unknown effect '{}'", key));
+ }
+
+ auto val = ruleValueToString(L);
+ if (!val) {
+ lua_pop(L, 1);
+ return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect '{}': {}", key, val.error()));
+ }
+
+ rule->addEffect(*dynamicEffect, *val);
+ hasRuleEffects = true;
+
+ lua_pop(L, 1);
+ continue;
+ }
+
+ auto val = UP<ILuaConfigValue>(desc->factory());
+ auto err = val->parse(L);
+ if (err.errorCode != PARSE_ERROR_OK) {
+ const bool allowLegacyString = (key == "max_size" || key == "min_size" || key == "border_color") && lua_isstring(L, -1);
+ if (allowLegacyString)
+ rule->addEffect(desc->effect, lua_tostring(L, -1));
+ else {
+ lua_pop(L, 1);
+ return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect '{}': {}", key, err.message));
+ }
+ } else
+ rule->addEffect(desc->effect, val->toString());
+
+ hasRuleEffects = true;
+ lua_pop(L, 1);
+ }
+
+ if (!hasRuleEffects)
+ return nullptr;
+ }
+
+ return rule;
+}