From 94b90a2cb0b4a0e13018cd5ebbf09394abf3b96b Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:10:25 +0100 Subject: config/lua: add ExpressionVec2, allow using a table for vec2 rules (#14197) --- src/config/lua/bindings/LuaBindingsConfigRules.cpp | 18 +--- src/config/lua/bindings/LuaBindingsInternal.cpp | 44 ++++----- src/config/lua/bindings/LuaBindingsInternal.hpp | 14 ++- src/config/lua/types/LuaConfigExpressionVec2.cpp | 100 +++++++++++++++++++++ src/config/lua/types/LuaConfigExpressionVec2.hpp | 26 ++++++ 5 files changed, 163 insertions(+), 39 deletions(-) create mode 100644 src/config/lua/types/LuaConfigExpressionVec2.cpp create mode 100644 src/config/lua/types/LuaConfigExpressionVec2.hpp (limited to 'src/config/lua') diff --git a/src/config/lua/bindings/LuaBindingsConfigRules.cpp b/src/config/lua/bindings/LuaBindingsConfigRules.cpp index 88761f05..52ecaf69 100644 --- a/src/config/lua/bindings/LuaBindingsConfigRules.cpp +++ b/src/config/lua/bindings/LuaBindingsConfigRules.cpp @@ -1076,21 +1076,9 @@ static int hlWindowRule(lua_State* L) { continue; } - auto val = UP(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) { - auto res = rule->addEffect(desc->effect, lua_tostring(L, -1)); - if (!res) - self->addError(std::format("{}: hl.window_rule: field '{}': {}", sourceInfo, key, res.error())); - } else - self->addError(std::format("{}: hl.window_rule: field '{}': {}", sourceInfo, key, err.message)); - } else { - auto res = rule->addEffect(desc->effect, val->toString()); - if (!res) - self->addError(std::format("{}: hl.window_rule: field '{}': {}", sourceInfo, key, res.error())); - } + auto res = Internal::addWindowRuleEffectFromLua(L, *desc, rule); + if (!res) + self->addError(std::format("{}: hl.window_rule: field '{}': {}", sourceInfo, key, res.error())); lua_pop(L, 1); } diff --git a/src/config/lua/bindings/LuaBindingsInternal.cpp b/src/config/lua/bindings/LuaBindingsInternal.cpp index e5a65bf7..a6af42f8 100644 --- a/src/config/lua/bindings/LuaBindingsInternal.cpp +++ b/src/config/lua/bindings/LuaBindingsInternal.cpp @@ -1,5 +1,7 @@ #include "LuaBindingsInternal.hpp" +#include "../../../desktop/rule/windowRule/WindowRule.hpp" + using namespace Config; using namespace Config::Lua; using namespace Config::Lua::Bindings; @@ -489,6 +491,24 @@ std::expected Internal::ruleValueToString(lua_State* L return std::unexpected("value must be a string, bool, or number"); } +std::expected Internal::addWindowRuleEffectFromLua(lua_State* L, const SWindowRuleEffectDesc& desc, const SP& rule) { + auto val = UP(desc.factory()); + auto err = val->parse(L); + + if (err.errorCode != PARSE_ERROR_OK) { + const bool allowLegacyString = desc.effect == WE::WINDOW_RULE_EFFECT_BORDER_COLOR && lua_isstring(L, -1); + if (!allowLegacyString) + return std::unexpected(err.message); + + return rule->addEffect(desc.effect, lua_tostring(L, -1)); + } + + if (const auto expr = dc(val.get()); expr) + return rule->addEffect(desc.effect, expr->parsed()); + + return rule->addEffect(desc.effect, val->toString()); +} + std::expected, int> Internal::buildRuleFromTable(lua_State* L, int idx) { SP rule; @@ -538,26 +558,10 @@ std::expected, int> Internal::buildRuleFromTable( continue; } - auto val = UP(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) { - auto res = rule->addEffect(desc->effect, lua_tostring(L, -1)); - if (!res) { - lua_pop(L, 1); - return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect '{}': {}", key, res.error())); - } - } else { - lua_pop(L, 1); - return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect '{}': {}", key, err.message)); - } - } else { - auto res = rule->addEffect(desc->effect, val->toString()); - if (!res) { - lua_pop(L, 1); - return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect '{}': {}", key, res.error())); - } + auto res = Internal::addWindowRuleEffectFromLua(L, *desc, rule); + if (!res) { + lua_pop(L, 1); + return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect '{}': {}", key, res.error())); } hasRuleEffects = true; diff --git a/src/config/lua/bindings/LuaBindingsInternal.hpp b/src/config/lua/bindings/LuaBindingsInternal.hpp index fc07515f..5d4a8d87 100644 --- a/src/config/lua/bindings/LuaBindingsInternal.hpp +++ b/src/config/lua/bindings/LuaBindingsInternal.hpp @@ -10,6 +10,7 @@ #include "../types/LuaConfigInt.hpp" #include "../types/LuaConfigString.hpp" #include "../types/LuaConfigVec2.hpp" +#include "../types/LuaConfigExpressionVec2.hpp" #include "../../../Compositor.hpp" #include "../../../helpers/MiscFunctions.hpp" @@ -31,6 +32,10 @@ extern "C" { #include } +namespace Desktop::Rule { + class CWindowRule; +} + namespace Config::Lua::Bindings::Internal { struct SWindowRuleEffectDesc { @@ -51,8 +56,8 @@ namespace Config::Lua::Bindings::Internal { {"no_initial_focus", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NOINITIALFOCUS}, {"pin", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_PIN}, {"fullscreen_state", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_FULLSCREENSTATE}, - {"move", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_MOVE}, - {"size", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_SIZE}, + {"move", []() -> ILuaConfigValue* { return new CLuaConfigExpressionVec2(); }, WE::WINDOW_RULE_EFFECT_MOVE}, + {"size", []() -> ILuaConfigValue* { return new CLuaConfigExpressionVec2(); }, WE::WINDOW_RULE_EFFECT_SIZE}, {"monitor", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_MONITOR}, {"workspace", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_WORKSPACE}, {"group", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_GROUP}, @@ -69,8 +74,8 @@ namespace Config::Lua::Bindings::Internal { {"idle_inhibit", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_IDLE_INHIBIT}, {"opacity", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_OPACITY}, {"tag", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_TAG}, - {"max_size", []() -> ILuaConfigValue* { return new CLuaConfigVec2({0, 0}); }, WE::WINDOW_RULE_EFFECT_MAX_SIZE}, - {"min_size", []() -> ILuaConfigValue* { return new CLuaConfigVec2({0, 0}); }, WE::WINDOW_RULE_EFFECT_MIN_SIZE}, + {"max_size", []() -> ILuaConfigValue* { return new CLuaConfigExpressionVec2(); }, WE::WINDOW_RULE_EFFECT_MAX_SIZE}, + {"min_size", []() -> ILuaConfigValue* { return new CLuaConfigExpressionVec2(); }, WE::WINDOW_RULE_EFFECT_MIN_SIZE}, {"border_color", []() -> ILuaConfigValue* { return new CLuaConfigGradient(CHyprColor(0xFF000000)); }, WE::WINDOW_RULE_EFFECT_BORDER_COLOR}, {"persistent_size", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_PERSISTENT_SIZE}, {"allows_input", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_ALLOWS_INPUT}, @@ -140,6 +145,7 @@ namespace Config::Lua::Bindings::Internal { Config::Actions::eTogglableAction tableToggleAction(lua_State* L, int idx, const char* field = "action"); std::expected ruleValueToString(lua_State* L); + std::expected addWindowRuleEffectFromLua(lua_State* L, const SWindowRuleEffectDesc& desc, const SP& rule); std::expected, int> buildRuleFromTable(lua_State* L, int idx); int configError(lua_State* L, std::string s, Config::Actions::eActionErrorLevel level = Config::Actions::eActionErrorLevel::ERROR, diff --git a/src/config/lua/types/LuaConfigExpressionVec2.cpp b/src/config/lua/types/LuaConfigExpressionVec2.cpp new file mode 100644 index 00000000..baabf02f --- /dev/null +++ b/src/config/lua/types/LuaConfigExpressionVec2.cpp @@ -0,0 +1,100 @@ +#include "LuaConfigExpressionVec2.hpp" + +#include +#include + +using namespace Config; +using namespace Config::Lua; + +CLuaConfigExpressionVec2::CLuaConfigExpressionVec2(Math::SExpressionVec2 def) : m_default(std::move(def)), m_data(m_default) { + ; +} + +static std::expected expressionVec2ElementToString(lua_State* s, int idx) { + if (lua_isinteger(s, idx)) + return std::to_string(lua_tointeger(s, idx)); + + if (lua_isnumber(s, idx)) + return std::format("{}", lua_tonumber(s, idx)); + + if (lua_isstring(s, idx)) + return std::string{lua_tostring(s, idx)}; + + return std::unexpected("expression vec2 elements must be strings or numbers"); +} + +SParseError CLuaConfigExpressionVec2::parse(lua_State* s) { + Math::SExpressionVec2 vec; + + if (lua_isstring(s, -1)) { + auto parsed = Math::parseExpressionVec2(lua_tostring(s, -1)); + if (!parsed) + return {.errorCode = PARSE_ERROR_BAD_VALUE, .message = parsed.error()}; + + vec = std::move(*parsed); + } else { + if (!lua_istable(s, -1)) + return {.errorCode = PARSE_ERROR_BAD_TYPE, .message = "expression vec2 type requires an array or string"}; + + if (lua_rawlen(s, -1) != 2) + return {.errorCode = PARSE_ERROR_BAD_VALUE, .message = "expression vec2 type requires exactly 2 elements"}; + + lua_rawgeti(s, -1, 1); + auto x = expressionVec2ElementToString(s, -1); + lua_pop(s, 1); + if (!x) + return {.errorCode = PARSE_ERROR_BAD_TYPE, .message = x.error()}; + + lua_rawgeti(s, -1, 2); + auto y = expressionVec2ElementToString(s, -1); + lua_pop(s, 1); + if (!y) + return {.errorCode = PARSE_ERROR_BAD_TYPE, .message = y.error()}; + + if (x->empty() || y->empty()) + return {.errorCode = PARSE_ERROR_BAD_VALUE, .message = "expression vec2 elements must not be empty"}; + + vec = {std::move(*x), std::move(*y)}; + } + + m_data = std::move(vec); + m_bSetByUser = true; + + return {.errorCode = PARSE_ERROR_OK}; +} + +const std::type_info* CLuaConfigExpressionVec2::underlying() { + return &typeid(decltype(m_data)); +} + +void const* CLuaConfigExpressionVec2::data() { + return &m_data; +} + +std::string CLuaConfigExpressionVec2::toString() { + return m_data.toString(); +} + +void CLuaConfigExpressionVec2::push(lua_State* s) { + lua_createtable(s, 2, 2); + + lua_pushstring(s, m_data.x.c_str()); + lua_rawseti(s, -2, 1); + + lua_pushstring(s, m_data.y.c_str()); + lua_rawseti(s, -2, 2); + + lua_pushstring(s, m_data.x.c_str()); + lua_setfield(s, -2, "x"); + + lua_pushstring(s, m_data.y.c_str()); + lua_setfield(s, -2, "y"); +} + +const Math::SExpressionVec2& CLuaConfigExpressionVec2::parsed() { + return m_data; +} + +void CLuaConfigExpressionVec2::reset() { + m_data = m_default; +} diff --git a/src/config/lua/types/LuaConfigExpressionVec2.hpp b/src/config/lua/types/LuaConfigExpressionVec2.hpp new file mode 100644 index 00000000..cf8b398d --- /dev/null +++ b/src/config/lua/types/LuaConfigExpressionVec2.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "LuaConfigValue.hpp" + +#include "../../../helpers/math/Expression.hpp" + +namespace Config::Lua { + class CLuaConfigExpressionVec2 : public ILuaConfigValue { + public: + CLuaConfigExpressionVec2(Math::SExpressionVec2 def = {}); + virtual ~CLuaConfigExpressionVec2() = default; + + virtual SParseError parse(lua_State* s); + virtual const std::type_info* underlying(); + virtual void const* data(); + virtual std::string toString(); + virtual void push(lua_State* s); + virtual void reset(); + + const Math::SExpressionVec2& parsed(); + + private: + Math::SExpressionVec2 m_default; + Math::SExpressionVec2 m_data; + }; +}; -- cgit v1.2.3