diff options
| author | Vaxry <43317083+vaxerski@users.noreply.github.com> | 2026-04-28 18:10:25 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-28 18:10:25 +0100 |
| commit | 94b90a2cb0b4a0e13018cd5ebbf09394abf3b96b (patch) | |
| tree | 484d32baaab1acdaab7a817c86aa89f117a21059 /src | |
| parent | ab75bbfbecdfb4a0355106f740944c87cb9edd4c (diff) | |
config/lua: add ExpressionVec2, allow using a table for vec2 rules (#14197)
Diffstat (limited to 'src')
| -rw-r--r-- | src/config/lua/bindings/LuaBindingsConfigRules.cpp | 18 | ||||
| -rw-r--r-- | src/config/lua/bindings/LuaBindingsInternal.cpp | 44 | ||||
| -rw-r--r-- | src/config/lua/bindings/LuaBindingsInternal.hpp | 14 | ||||
| -rw-r--r-- | src/config/lua/types/LuaConfigExpressionVec2.cpp | 100 | ||||
| -rw-r--r-- | src/config/lua/types/LuaConfigExpressionVec2.hpp | 26 | ||||
| -rw-r--r-- | src/desktop/rule/RuleWithEffects.hpp | 7 | ||||
| -rw-r--r-- | src/desktop/rule/windowRule/WindowRule.cpp | 24 | ||||
| -rw-r--r-- | src/desktop/rule/windowRule/WindowRule.hpp | 8 | ||||
| -rw-r--r-- | src/desktop/rule/windowRule/WindowRuleApplicator.cpp | 34 | ||||
| -rw-r--r-- | src/desktop/rule/windowRule/WindowRuleApplicator.hpp | 31 | ||||
| -rw-r--r-- | src/desktop/view/Window.cpp | 18 | ||||
| -rw-r--r-- | src/desktop/view/Window.hpp | 1 | ||||
| -rw-r--r-- | src/helpers/math/Expression.cpp | 42 | ||||
| -rw-r--r-- | src/helpers/math/Expression.hpp | 14 | ||||
| -rw-r--r-- | src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp | 12 |
15 files changed, 312 insertions, 81 deletions
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<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) { - 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<std::string, std::string> Internal::ruleValueToString(lua_State* L return std::unexpected("value must be a string, bool, or number"); } +std::expected<void, std::string> Internal::addWindowRuleEffectFromLua(lua_State* L, const SWindowRuleEffectDesc& desc, const SP<Desktop::Rule::CWindowRule>& rule) { + auto val = UP<ILuaConfigValue>(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<CLuaConfigExpressionVec2*>(val.get()); expr) + return rule->addEffect(desc.effect, expr->parsed()); + + return rule->addEffect(desc.effect, val->toString()); +} + std::expected<SP<Desktop::Rule::CWindowRule>, int> Internal::buildRuleFromTable(lua_State* L, int idx) { SP<Desktop::Rule::CWindowRule> rule; @@ -538,26 +558,10 @@ std::expected<SP<Desktop::Rule::CWindowRule>, int> Internal::buildRuleFromTable( 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) { - 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 <lauxlib.h> } +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<std::string, std::string> ruleValueToString(lua_State* L); + std::expected<void, std::string> addWindowRuleEffectFromLua(lua_State* L, const SWindowRuleEffectDesc& desc, const SP<Desktop::Rule::CWindowRule>& rule); std::expected<SP<Desktop::Rule::CWindowRule>, 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 <format> +#include <utility> + +using namespace Config; +using namespace Config::Lua; + +CLuaConfigExpressionVec2::CLuaConfigExpressionVec2(Math::SExpressionVec2 def) : m_default(std::move(def)), m_data(m_default) { + ; +} + +static std::expected<std::string, std::string> 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; + }; +}; diff --git a/src/desktop/rule/RuleWithEffects.hpp b/src/desktop/rule/RuleWithEffects.hpp index 17d755e8..17805e9b 100644 --- a/src/desktop/rule/RuleWithEffects.hpp +++ b/src/desktop/rule/RuleWithEffects.hpp @@ -44,6 +44,13 @@ namespace Desktop::Rule { virtual std::expected<valueType, std::string> parseEffect(storageType e, const std::string& result) = 0; + std::expected<void, std::string> addParsedEffect(storageType e, valueType value, std::string raw = {}) { + m_effects.emplace_back(TEffect{.key = e, .raw = std::move(raw), .value = std::move(value)}); + m_effectSet.emplace(e); + + return {}; + } + private: std::vector<TEffect> m_effects; std::unordered_set<storageType> m_effectSet; diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp index c2b520c0..10d39383 100644 --- a/src/desktop/rule/windowRule/WindowRule.cpp +++ b/src/desktop/rule/windowRule/WindowRule.cpp @@ -259,13 +259,19 @@ static std::expected<WindowRuleEffectValue, std::string> parseWindowRuleEffect(C case WINDOW_RULE_EFFECT_MOVE: case WINDOW_RULE_EFFECT_SIZE: + case WINDOW_RULE_EFFECT_MAX_SIZE: + case WINDOW_RULE_EFFECT_MIN_SIZE: { + auto parsed = Math::parseExpressionVec2(raw); + if (!parsed) + return std::unexpected(std::format("{} rule \"{}\" failed with: {}", EFFECT_NAME, raw, parsed.error())); + return *parsed; + } + case WINDOW_RULE_EFFECT_MONITOR: case WINDOW_RULE_EFFECT_WORKSPACE: case WINDOW_RULE_EFFECT_GROUP: case WINDOW_RULE_EFFECT_ANIMATION: - case WINDOW_RULE_EFFECT_TAG: - case WINDOW_RULE_EFFECT_MAX_SIZE: - case WINDOW_RULE_EFFECT_MIN_SIZE: return std::string{raw}; + case WINDOW_RULE_EFFECT_TAG: return std::string{raw}; case WINDOW_RULE_EFFECT_SUPPRESSEVENT: return parseStringList(raw); @@ -334,6 +340,18 @@ CWindowRule::CWindowRule(const std::string& name) : CRuleWithEffects<SWindowRule ; } +std::expected<void, std::string> CWindowRule::addEffect(CWindowRule::storageType e, const Math::SExpressionVec2& expr) { + switch (e) { + case WINDOW_RULE_EFFECT_MOVE: + case WINDOW_RULE_EFFECT_SIZE: + case WINDOW_RULE_EFFECT_MAX_SIZE: + case WINDOW_RULE_EFFECT_MIN_SIZE: break; + default: return std::unexpected(std::format("{} is not an expression vec2 window rule effect", windowEffects()->get(e))); + } + + return addParsedEffect(e, expr, expr.toString()); +} + std::expected<WindowRuleEffectValue, std::string> CWindowRule::parseEffect(CWindowRule::storageType e, const std::string& result) { return parseWindowRuleEffect(e, result); } diff --git a/src/desktop/rule/windowRule/WindowRule.hpp b/src/desktop/rule/windowRule/WindowRule.hpp index 61fc1520..3701ae35 100644 --- a/src/desktop/rule/windowRule/WindowRule.hpp +++ b/src/desktop/rule/windowRule/WindowRule.hpp @@ -5,6 +5,7 @@ #include "../../types/OverridableVar.hpp" #include "WindowRuleEffectContainer.hpp" #include "../../../config/shared/complex/ComplexDataTypes.hpp" +#include "../../../helpers/math/Expression.hpp" #include "../../../helpers/math/Math.hpp" #include <expected> @@ -30,7 +31,8 @@ namespace Desktop::Rule { std::optional<Config::CGradientValueData> inactive; }; - using WindowRuleEffectValue = std::variant<std::monostate, bool, int64_t, float, std::string, std::vector<std::string>, SFullscreenStateRule, SOpacityRule, SBorderColorRule>; + using WindowRuleEffectValue = + std::variant<std::monostate, bool, int64_t, float, std::string, std::vector<std::string>, SFullscreenStateRule, SOpacityRule, SBorderColorRule, Math::SExpressionVec2>; struct SWindowRuleEffect { using storageType = CWindowRuleEffectContainer::storageType; @@ -50,12 +52,16 @@ namespace Desktop::Rule { CWindowRule(const std::string& name = ""); virtual ~CWindowRule() = default; + using Base::addEffect; + CWindowRule(const CWindowRule&) = default; CWindowRule(CWindowRule&) = default; CWindowRule(CWindowRule&&) = default; static std::expected<SP<CWindowRule>, std::string> buildFromExecString(std::string&&); + std::expected<void, std::string> addEffect(storageType e, const Math::SExpressionVec2& expr); + bool matches(PHLWINDOW w, bool allowEnvLookup = false); private: diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp index b51049e6..c19c6338 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp @@ -168,9 +168,13 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const if (!m_window) break; - const auto VEC = m_window->calculateExpression(effect); + const auto& expr = std::get<Math::SExpressionVec2>(value); + if (expr.empty()) + break; + + const auto VEC = m_window->calculateExpression(expr); if (!VEC) { - Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect); + Log::logger->log(Log::ERR, "failed to parse {} as an expression", expr.toString()); break; } if (VEC->x < 1 || VEC->y < 1) { @@ -182,7 +186,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const if (*PCLAMP_TILED || m_window->m_isFloating) m_window->clampWindowSize(std::nullopt, m_maxSize.first.value()); - } catch (std::exception& e) { Log::logger->log(Log::ERR, "maxsize rule \"{}\" failed with: {}", effect, e.what()); } + } catch (std::exception& e) { Log::logger->log(Log::ERR, "maxsize rule \"{}\" failed with: {}", std::get<Math::SExpressionVec2>(value).toString(), e.what()); } m_maxSize.second = rule->getPropertiesMask(); break; } @@ -193,9 +197,13 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const if (!m_window) break; - const auto VEC = m_window->calculateExpression(effect); + const auto& expr = std::get<Math::SExpressionVec2>(value); + if (expr.empty()) + break; + + const auto VEC = m_window->calculateExpression(expr); if (!VEC) { - Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect); + Log::logger->log(Log::ERR, "failed to parse {} as an expression", expr.toString()); break; } @@ -207,7 +215,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const m_minSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE); if (*PCLAMP_TILED || m_window->m_isFloating) m_window->clampWindowSize(m_minSize.first.value(), std::nullopt); - } catch (std::exception& e) { Log::logger->log(Log::ERR, "minsize rule \"{}\" failed with: {}", effect, e.what()); } + } catch (std::exception& e) { Log::logger->log(Log::ERR, "minsize rule \"{}\" failed with: {}", std::get<Math::SExpressionVec2>(value).toString(), e.what()); } m_minSize.second = rule->getPropertiesMask(); break; } @@ -385,15 +393,23 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const } case WINDOW_RULE_EFFECT_MOVE: { static_.center = std::nullopt; - static_.position = std::get<std::string>(value); + const auto& expr = std::get<Math::SExpressionVec2>(value); + if (expr.empty()) + static_.position.reset(); + else + static_.position = expr; break; } case WINDOW_RULE_EFFECT_SIZE: { - static_.size = std::get<std::string>(value); + const auto& expr = std::get<Math::SExpressionVec2>(value); + if (expr.empty()) + static_.size.reset(); + else + static_.size = expr; break; } case WINDOW_RULE_EFFECT_CENTER: { - static_.position.clear(); + static_.position.reset(); static_.center = std::get<bool>(value); break; } diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp index 1e52ed5c..f5e43609 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp @@ -8,6 +8,7 @@ #include "../Rule.hpp" #include "../../types/OverridableVar.hpp" #include "../../../helpers/math/Math.hpp" +#include "../../../helpers/math/Expression.hpp" #include "../../../helpers/TagKeeper.hpp" #include "../../../config/shared/complex/ComplexDataTypes.hpp" @@ -38,25 +39,25 @@ namespace Desktop::Rule { // static props struct { - std::string monitor, workspace, group; + std::string monitor, workspace, group; - std::optional<bool> floating; - std::optional<bool> fullscreen; - std::optional<bool> maximize; - std::optional<bool> pseudo; - std::optional<bool> pin; - std::optional<bool> noInitialFocus; - std::optional<bool> center; + std::optional<bool> floating; + std::optional<bool> fullscreen; + std::optional<bool> maximize; + std::optional<bool> pseudo; + std::optional<bool> pin; + std::optional<bool> noInitialFocus; + std::optional<bool> center; - std::optional<int> fullscreenStateClient; - std::optional<int> fullscreenStateInternal; - std::optional<int> content; - std::optional<int> noCloseFor; + std::optional<int> fullscreenStateClient; + std::optional<int> fullscreenStateInternal; + std::optional<int> content; + std::optional<int> noCloseFor; - std::string size, position; - std::optional<float> scrollingWidth; + std::optional<Math::SExpressionVec2> size, position; + std::optional<float> scrollingWidth; - std::vector<std::string> suppressEvent; + std::vector<std::string> suppressEvent; } static_; struct SCustomPropContainer { diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp index 53347722..e75c93a5 100644 --- a/src/desktop/view/Window.cpp +++ b/src/desktop/view/Window.cpp @@ -1645,12 +1645,16 @@ std::optional<double> CWindow::calculateSingleExpr(const std::string& s) { } std::optional<Vector2D> CWindow::calculateExpression(const std::string& s) { - auto spacePos = s.find(' '); - if (spacePos == std::string::npos) + const auto parsed = Math::parseExpressionVec2(s); + if (!parsed) return std::nullopt; - const auto LHS = calculateSingleExpr(s.substr(0, spacePos)); - const auto RHS = calculateSingleExpr(s.substr(spacePos + 1)); + return calculateExpression(*parsed); +} + +std::optional<Vector2D> CWindow::calculateExpression(const Math::SExpressionVec2& expr) { + const auto LHS = calculateSingleExpr(expr.x); + const auto RHS = calculateSingleExpr(expr.y); if (!LHS || !RHS) return std::nullopt; @@ -2011,10 +2015,10 @@ void CWindow::mapWindow() { } else { bool setPseudo = false; - if (!m_ruleApplicator->static_.size.empty()) { - const auto COMPUTED = calculateExpression(m_ruleApplicator->static_.size); + if (m_ruleApplicator->static_.size) { + const auto COMPUTED = calculateExpression(*m_ruleApplicator->static_.size); if (!COMPUTED) - Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.size); + Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.size->toString()); else { setPseudo = true; m_target->setPseudoSize(*COMPUTED); diff --git a/src/desktop/view/Window.hpp b/src/desktop/view/Window.hpp index 35c08326..ff6fad96 100644 --- a/src/desktop/view/Window.hpp +++ b/src/desktop/view/Window.hpp @@ -356,6 +356,7 @@ namespace Desktop::View { SP<CWLSurfaceResource> getSolitaryResource(); Vector2D getReportedSize(); std::optional<Vector2D> calculateExpression(const std::string& s); + std::optional<Vector2D> calculateExpression(const Math::SExpressionVec2& expr); std::optional<Vector2D> minSize(); std::optional<Vector2D> maxSize(); SP<Layout::ITarget> layoutTarget(); diff --git a/src/helpers/math/Expression.cpp b/src/helpers/math/Expression.cpp index 3c0bee91..2289b85e 100644 --- a/src/helpers/math/Expression.cpp +++ b/src/helpers/math/Expression.cpp @@ -2,8 +2,50 @@ #include "muParser.h" #include "../../debug/log/Logger.hpp" +#include <cctype> +#include <format> + using namespace Math; +static std::string_view trimExprView(std::string_view s) { + while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front()))) + s.remove_prefix(1); + + while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back()))) + s.remove_suffix(1); + + return s; +} + +bool SExpressionVec2::empty() const { + return x.empty() || y.empty(); +} + +std::string SExpressionVec2::toString() const { + if (empty()) + return ""; + + return std::format("{} {}", x, y); +} + +std::expected<SExpressionVec2, std::string> Math::parseExpressionVec2(std::string_view raw) { + raw = trimExprView(raw); + if (raw.empty()) + return SExpressionVec2{}; + + const auto spacePos = raw.find_first_of(" \t\n\r\f\v"); + if (spacePos == std::string_view::npos) + return std::unexpected("expression vec2 requires two expressions separated by whitespace"); + + auto lhs = trimExprView(raw.substr(0, spacePos)); + auto rhs = trimExprView(raw.substr(spacePos + 1)); + + if (lhs.empty() || rhs.empty()) + return std::unexpected("expression vec2 requires two non-empty expressions"); + + return SExpressionVec2{std::string{lhs}, std::string{rhs}}; +} + CExpression::CExpression() : m_parser(makeUnique<mu::Parser>()) { ; } diff --git a/src/helpers/math/Expression.hpp b/src/helpers/math/Expression.hpp index 1780e3ee..631d78ad 100644 --- a/src/helpers/math/Expression.hpp +++ b/src/helpers/math/Expression.hpp @@ -1,14 +1,26 @@ #pragma once #include "../memory/Memory.hpp" +#include <expected> #include <string> #include <optional> +#include <string_view> namespace mu { class Parser; }; namespace Math { + struct SExpressionVec2 { + std::string x; + std::string y; + + bool empty() const; + std::string toString() const; + }; + + std::expected<SExpressionVec2, std::string> parseExpressionVec2(std::string_view raw); + class CExpression { public: CExpression(); @@ -25,4 +37,4 @@ namespace Math { private: UP<mu::Parser> m_parser; }; -};
\ No newline at end of file +}; diff --git a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp index 0d069e4f..f8f89fcb 100644 --- a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp +++ b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp @@ -50,10 +50,10 @@ void CDefaultFloatingAlgorithm::newTarget(SP<ITarget> target) { // set this here so that expressions can use it. This could be wrong of course. WINDOW->m_realSize->setValueAndWarp(DESIRED_GEOM ? DESIRED_GEOM->size : DEFAULT_SIZE); - if (!WINDOW->m_ruleApplicator->static_.size.empty()) { - const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.size); + if (WINDOW->m_ruleApplicator->static_.size) { + const auto COMPUTED = WINDOW->calculateExpression(*WINDOW->m_ruleApplicator->static_.size); if (!COMPUTED) - Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.size); + Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.size->toString()); else { windowGeometry.w = COMPUTED->x; windowGeometry.h = COMPUTED->y; @@ -63,10 +63,10 @@ void CDefaultFloatingAlgorithm::newTarget(SP<ITarget> target) { } } - if (!WINDOW->m_ruleApplicator->static_.position.empty()) { - const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.position); + if (WINDOW->m_ruleApplicator->static_.position) { + const auto COMPUTED = WINDOW->calculateExpression(*WINDOW->m_ruleApplicator->static_.position); if (!COMPUTED) - Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.position); + Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.position->toString()); else { windowGeometry.x = COMPUTED->x + MONITOR_POS.x; windowGeometry.y = COMPUTED->y + MONITOR_POS.y; |
