summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2026-04-28 18:10:25 +0100
committerGitHub <noreply@github.com>2026-04-28 18:10:25 +0100
commit94b90a2cb0b4a0e13018cd5ebbf09394abf3b96b (patch)
tree484d32baaab1acdaab7a817c86aa89f117a21059 /src
parentab75bbfbecdfb4a0355106f740944c87cb9edd4c (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.cpp18
-rw-r--r--src/config/lua/bindings/LuaBindingsInternal.cpp44
-rw-r--r--src/config/lua/bindings/LuaBindingsInternal.hpp14
-rw-r--r--src/config/lua/types/LuaConfigExpressionVec2.cpp100
-rw-r--r--src/config/lua/types/LuaConfigExpressionVec2.hpp26
-rw-r--r--src/desktop/rule/RuleWithEffects.hpp7
-rw-r--r--src/desktop/rule/windowRule/WindowRule.cpp24
-rw-r--r--src/desktop/rule/windowRule/WindowRule.hpp8
-rw-r--r--src/desktop/rule/windowRule/WindowRuleApplicator.cpp34
-rw-r--r--src/desktop/rule/windowRule/WindowRuleApplicator.hpp31
-rw-r--r--src/desktop/view/Window.cpp18
-rw-r--r--src/desktop/view/Window.hpp1
-rw-r--r--src/helpers/math/Expression.cpp42
-rw-r--r--src/helpers/math/Expression.hpp14
-rw-r--r--src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp12
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;