summaryrefslogtreecommitdiff
path: root/src/desktop/rule/windowRule/WindowRuleApplicator.cpp
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2025-11-17 18:34:02 +0000
committerGitHub <noreply@github.com>2025-11-17 18:34:02 +0000
commitc2670e9ab90bd657e87a1da2c5322e9007dce01f (patch)
treecd033fe3a9b87b1b0d457df043ecc50929ad3c8d /src/desktop/rule/windowRule/WindowRuleApplicator.cpp
parent95ee08b3403b90e2930d46425cada1d280f3524e (diff)
windowrules: rewrite completely (#12269)
Reworks the window rule syntax completely --------- Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
Diffstat (limited to 'src/desktop/rule/windowRule/WindowRuleApplicator.cpp')
-rw-r--r--src/desktop/rule/windowRule/WindowRuleApplicator.cpp642
1 files changed, 642 insertions, 0 deletions
diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp
new file mode 100644
index 00000000..e6e0c655
--- /dev/null
+++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp
@@ -0,0 +1,642 @@
+#include "WindowRuleApplicator.hpp"
+#include "WindowRule.hpp"
+#include "../Engine.hpp"
+#include "../utils/SetUtils.hpp"
+#include "../../Window.hpp"
+#include "../../types/OverridableVar.hpp"
+#include "../../../managers/LayoutManager.hpp"
+
+#include <hyprutils/string/String.hpp>
+
+using namespace Hyprutils::String;
+
+using namespace Desktop;
+using namespace Desktop::Rule;
+
+CWindowRuleApplicator::CWindowRuleApplicator(PHLWINDOW w) : m_window(w) {
+ ;
+}
+
+void CWindowRuleApplicator::resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio) {
+ // TODO: fucking kill me, is there a better way to do this?
+
+#define UNSET(x) \
+ if (m_##x.second & props) { \
+ if (prio == Types::PRIORITY_WINDOW_RULE) \
+ m_##x.second &= ~props; \
+ m_##x.first.unset(prio); \
+ }
+
+ UNSET(alpha)
+ UNSET(alphaInactive)
+ UNSET(alphaFullscreen)
+ UNSET(allowsInput)
+ UNSET(decorate)
+ UNSET(focusOnActivate)
+ UNSET(keepAspectRatio)
+ UNSET(nearestNeighbor)
+ UNSET(noAnim)
+ UNSET(noBlur)
+ UNSET(noDim)
+ UNSET(noFocus)
+ UNSET(noMaxSize)
+ UNSET(noShadow)
+ UNSET(noShortcutsInhibit)
+ UNSET(opaque)
+ UNSET(dimAround)
+ UNSET(RGBX)
+ UNSET(syncFullscreen)
+ UNSET(tearing)
+ UNSET(xray)
+ UNSET(renderUnfocused)
+ UNSET(noFollowMouse)
+ UNSET(noScreenShare)
+ UNSET(noVRR)
+ UNSET(persistentSize)
+ UNSET(stayFocused)
+ UNSET(idleInhibitMode)
+ UNSET(borderSize)
+ UNSET(rounding)
+ UNSET(roundingPower)
+ UNSET(scrollMouse)
+ UNSET(scrollTouchpad)
+ UNSET(animationStyle)
+ UNSET(maxSize)
+ UNSET(minSize)
+ UNSET(activeBorderColor)
+ UNSET(inactiveBorderColor)
+
+#undef UNSET
+
+ if (prio == Types::PRIORITY_WINDOW_RULE) {
+ std::erase_if(m_dynamicTags, [props, this](const auto& el) {
+ const bool REMOVE = el.second & props;
+
+ if (REMOVE)
+ m_tagKeeper.removeDynamicTag(el.first);
+
+ return REMOVE;
+ });
+
+ std::erase_if(m_otherProps.props, [props](const auto& el) { return !el.second || el.second->propMask & props; });
+ }
+}
+
+CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const SP<CWindowRule>& rule) {
+ SRuleResult result;
+
+ for (const auto& [key, effect] : rule->effects()) {
+ switch (key) {
+ default: {
+ if (key <= WINDOW_RULE_EFFECT_LAST_STATIC) {
+ Debug::log(TRACE, "CWindowRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic", sc<std::underlying_type_t<eWindowRuleEffect>>(key));
+ break;
+ }
+
+ // custom type, add to our vec
+ if (!m_otherProps.props.contains(key)) {
+ m_otherProps.props.emplace(key,
+ makeUnique<SCustomPropContainer>(SCustomPropContainer{
+ .idx = key,
+ .propMask = rule->getPropertiesMask(),
+ .effect = effect,
+ }));
+ } else {
+ auto& e = m_otherProps.props[key];
+ e->propMask |= rule->getPropertiesMask();
+ e->effect = effect;
+ }
+
+ break;
+ }
+
+ case WINDOW_RULE_EFFECT_NONE: {
+ Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: BUG THIS: WINDOW_RULE_EFFECT_NONE??");
+ break;
+ }
+ case WINDOW_RULE_EFFECT_ROUNDING: {
+ try {
+ m_rounding.first.set(std::stoull(effect), Types::PRIORITY_WINDOW_RULE);
+ m_rounding.second |= rule->getPropertiesMask();
+ } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding {}", effect); }
+ break;
+ }
+ case WINDOW_RULE_EFFECT_ROUNDING_POWER: {
+ try {
+ m_roundingPower.first.set(std::clamp(std::stof(effect), 1.F, 10.F), Types::PRIORITY_WINDOW_RULE);
+ m_roundingPower.second |= rule->getPropertiesMask();
+ } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding_power {}", effect); }
+ break;
+ }
+ case WINDOW_RULE_EFFECT_PERSISTENT_SIZE: {
+ try {
+ m_persistentSize.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);
+ m_persistentSize.second |= rule->getPropertiesMask();
+ } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding_power {}", effect); }
+ break;
+ }
+ case WINDOW_RULE_EFFECT_ANIMATION: {
+ m_animationStyle.first.set(effect, Types::PRIORITY_WINDOW_RULE);
+ m_animationStyle.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_BORDER_COLOR: {
+ try {
+ // Each vector will only get used if it has at least one color
+ CGradientValueData activeBorderGradient = {};
+ CGradientValueData inactiveBorderGradient = {};
+ bool active = true;
+ CVarList colorsAndAngles = CVarList(trim(effect.substr(effect.find_first_of(' ') + 1)), 0, 's', true);
+
+ // Basic form has only two colors, everything else can be parsed as a gradient
+ if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) {
+ m_activeBorderColor.first =
+ Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[0]).value_or(0))), Types::PRIORITY_WINDOW_RULE);
+ m_inactiveBorderColor.first =
+ Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), Types::PRIORITY_WINDOW_RULE);
+ break;
+ }
+
+ for (auto const& token : colorsAndAngles) {
+ // The first angle, or an explicit "0deg", splits the two gradients
+ if (active && token.contains("deg")) {
+ activeBorderGradient.m_angle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0);
+ active = false;
+ } else if (token.contains("deg"))
+ inactiveBorderGradient.m_angle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0);
+ else if (active)
+ activeBorderGradient.m_colors.emplace_back(configStringToInt(token).value_or(0));
+ else
+ inactiveBorderGradient.m_colors.emplace_back(configStringToInt(token).value_or(0));
+ }
+
+ activeBorderGradient.updateColorsOk();
+
+ // Includes sanity checks for the number of colors in each gradient
+ if (activeBorderGradient.m_colors.size() > 10 || inactiveBorderGradient.m_colors.size() > 10)
+ Debug::log(WARN, "Bordercolor rule \"{}\" has more than 10 colors in one gradient, ignoring", effect);
+ else if (activeBorderGradient.m_colors.empty())
+ Debug::log(WARN, "Bordercolor rule \"{}\" has no colors, ignoring", effect);
+ else if (inactiveBorderGradient.m_colors.empty())
+ m_activeBorderColor.first = Types::COverridableVar(activeBorderGradient, Types::PRIORITY_WINDOW_RULE);
+ else {
+ m_activeBorderColor.first = Types::COverridableVar(activeBorderGradient, Types::PRIORITY_WINDOW_RULE);
+ m_inactiveBorderColor.first = Types::COverridableVar(inactiveBorderGradient, Types::PRIORITY_WINDOW_RULE);
+ }
+ } catch (std::exception& e) { Debug::log(ERR, "BorderColor rule \"{}\" failed with: {}", effect, e.what()); }
+ m_activeBorderColor.second = rule->getPropertiesMask();
+ m_inactiveBorderColor.second = rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_IDLE_INHIBIT: {
+ if (effect == "none")
+ m_idleInhibitMode.first.set(IDLEINHIBIT_NONE, Types::PRIORITY_WINDOW_RULE);
+ else if (effect == "always")
+ m_idleInhibitMode.first.set(IDLEINHIBIT_ALWAYS, Types::PRIORITY_WINDOW_RULE);
+ else if (effect == "focus")
+ m_idleInhibitMode.first.set(IDLEINHIBIT_FOCUS, Types::PRIORITY_WINDOW_RULE);
+ else if (effect == "fullscreen")
+ m_idleInhibitMode.first.set(IDLEINHIBIT_FULLSCREEN, Types::PRIORITY_WINDOW_RULE);
+ else
+ Debug::log(ERR, "Rule idleinhibit: unknown mode {}", effect);
+ m_idleInhibitMode.second = rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_OPACITY: {
+ try {
+ CVarList2 vars(std::string{effect}, 0, ' ');
+
+ int opacityIDX = 0;
+
+ for (const auto& r : vars) {
+ if (r == "opacity")
+ continue;
+
+ if (r == "override") {
+ if (opacityIDX == 1)
+ m_alpha.first = Types::COverridableVar(Types::SAlphaValue{.alpha = m_alpha.first.value().alpha, .overridden = true}, Types::PRIORITY_WINDOW_RULE);
+ else if (opacityIDX == 2)
+ m_alphaInactive.first =
+ Types::COverridableVar(Types::SAlphaValue{.alpha = m_alphaInactive.first.value().alpha, .overridden = true}, Types::PRIORITY_WINDOW_RULE);
+ else if (opacityIDX == 3)
+ m_alphaFullscreen.first =
+ Types::COverridableVar(Types::SAlphaValue{.alpha = m_alphaFullscreen.first.value().alpha, .overridden = true}, Types::PRIORITY_WINDOW_RULE);
+ } else {
+ if (opacityIDX == 0)
+ m_alpha.first = Types::COverridableVar(Types::SAlphaValue{.alpha = std::stof(std::string{r}), .overridden = false}, Types::PRIORITY_WINDOW_RULE);
+ else if (opacityIDX == 1)
+ m_alphaInactive.first =
+ Types::COverridableVar(Types::SAlphaValue{.alpha = std::stof(std::string{r}), .overridden = false}, Types::PRIORITY_WINDOW_RULE);
+ else if (opacityIDX == 2)
+ m_alphaFullscreen.first =
+ Types::COverridableVar(Types::SAlphaValue{.alpha = std::stof(std::string{r}), .overridden = false}, Types::PRIORITY_WINDOW_RULE);
+ else
+ throw std::runtime_error("more than 3 alpha values");
+
+ opacityIDX++;
+ }
+ }
+
+ if (opacityIDX == 1) {
+ m_alphaInactive.first = m_alpha.first;
+ m_alphaFullscreen.first = m_alpha.first;
+ }
+ } catch (std::exception& e) { Debug::log(ERR, "Opacity rule \"{}\" failed with: {}", effect, e.what()); }
+ m_alpha.second = rule->getPropertiesMask();
+ m_alphaInactive.second = rule->getPropertiesMask();
+ m_alphaFullscreen.second = rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_TAG: {
+ m_dynamicTags.emplace_back(std::make_pair<>(effect, rule->getPropertiesMask()));
+ m_tagKeeper.applyTag(effect, true);
+ result.tagsChanged = true;
+ break;
+ }
+ case WINDOW_RULE_EFFECT_MAX_SIZE: {
+ try {
+ static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>("misc:size_limits_tiled");
+
+ if (!m_window)
+ break;
+
+ if (!m_window->m_isFloating && !sc<bool>(*PCLAMP_TILED))
+ break;
+
+ const auto VEC = configStringToVector2D(effect);
+ if (VEC.x < 1 || VEC.y < 1) {
+ Debug::log(ERR, "Invalid size for maxsize");
+ break;
+ }
+
+ m_maxSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE);
+ m_window->clampWindowSize(std::nullopt, m_maxSize.first.value());
+
+ } catch (std::exception& e) { Debug::log(ERR, "maxsize rule \"{}\" failed with: {}", effect, e.what()); }
+ m_maxSize.second = rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_MIN_SIZE: {
+ try {
+ static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>("misc:size_limits_tiled");
+
+ if (!m_window)
+ break;
+
+ if (!m_window->m_isFloating && !sc<bool>(*PCLAMP_TILED))
+ break;
+
+ const auto VEC = configStringToVector2D(effect);
+ if (VEC.x < 1 || VEC.y < 1) {
+ Debug::log(ERR, "Invalid size for maxsize");
+ break;
+ }
+
+ m_minSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE);
+ m_window->clampWindowSize(std::nullopt, m_minSize.first.value());
+ } catch (std::exception& e) { Debug::log(ERR, "minsize rule \"{}\" failed with: {}", effect, e.what()); }
+ m_minSize.second = rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_BORDER_SIZE: {
+ try {
+ auto oldBorderSize = m_borderSize.first.valueOrDefault();
+ m_borderSize.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);
+ m_borderSize.second |= rule->getPropertiesMask();
+ if (oldBorderSize != m_borderSize.first.valueOrDefault())
+ result.needsRelayout = true;
+ } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid border_size {}", effect); }
+ break;
+ }
+ case WINDOW_RULE_EFFECT_ALLOWS_INPUT: {
+ m_allowsInput.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_allowsInput.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_DIM_AROUND: {
+ m_dimAround.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_dimAround.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_DECORATE: {
+ m_decorate.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_decorate.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_FOCUS_ON_ACTIVATE: {
+ m_focusOnActivate.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_focusOnActivate.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_KEEP_ASPECT_RATIO: {
+ m_keepAspectRatio.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_keepAspectRatio.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NEAREST_NEIGHBOR: {
+ m_nearestNeighbor.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_nearestNeighbor.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_ANIM: {
+ m_noAnim.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noAnim.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_BLUR: {
+ m_noBlur.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noBlur.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_DIM: {
+ m_noDim.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noDim.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_FOCUS: {
+ m_noFocus.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noFocus.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_FOLLOW_MOUSE: {
+ m_noFollowMouse.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noFollowMouse.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_MAX_SIZE: {
+ m_noMaxSize.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noMaxSize.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_SHADOW: {
+ m_noShadow.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noShadow.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_SHORTCUTS_INHIBIT: {
+ m_noShortcutsInhibit.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noShortcutsInhibit.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_OPAQUE: {
+ m_opaque.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_opaque.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_FORCE_RGBX: {
+ m_RGBX.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_RGBX.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_SYNC_FULLSCREEN: {
+ m_syncFullscreen.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_syncFullscreen.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_IMMEDIATE: {
+ m_tearing.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_tearing.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_XRAY: {
+ m_xray.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_xray.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_RENDER_UNFOCUSED: {
+ m_renderUnfocused.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_renderUnfocused.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_SCREEN_SHARE: {
+ m_noScreenShare.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noScreenShare.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NO_VRR: {
+ m_noVRR.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_noVRR.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_STAY_FOCUSED: {
+ m_stayFocused.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
+ m_stayFocused.second |= rule->getPropertiesMask();
+ break;
+ }
+ case WINDOW_RULE_EFFECT_SCROLL_MOUSE: {
+ try {
+ m_scrollMouse.first.set(std::clamp(std::stof(effect), 0.01F, 10.F), Types::PRIORITY_WINDOW_RULE);
+ m_scrollMouse.second |= rule->getPropertiesMask();
+ } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid scroll_mouse {}", effect); }
+ break;
+ }
+ case WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD: {
+ try {
+ m_scrollTouchpad.first.set(std::clamp(std::stof(effect), 0.01F, 10.F), Types::PRIORITY_WINDOW_RULE);
+ m_scrollTouchpad.second |= rule->getPropertiesMask();
+ } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid scroll_touchpad {}", effect); }
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const SP<CWindowRule>& rule) {
+ for (const auto& [key, effect] : rule->effects()) {
+ switch (key) {
+ default: {
+ Debug::log(TRACE, "CWindowRuleApplicator::applyStaticRule: Skipping effect {}, not static", sc<std::underlying_type_t<eWindowRuleEffect>>(key));
+ break;
+ }
+
+ case WINDOW_RULE_EFFECT_FLOAT: {
+ static_.floating = truthy(effect);
+ break;
+ }
+ case WINDOW_RULE_EFFECT_TILE: {
+ static_.floating = !truthy(effect);
+ break;
+ }
+ case WINDOW_RULE_EFFECT_FULLSCREEN: {
+ static_.fullscreen = truthy(effect);
+ break;
+ }
+ case WINDOW_RULE_EFFECT_MAXIMIZE: {
+ static_.maximize = truthy(effect);
+ break;
+ }
+ case WINDOW_RULE_EFFECT_FULLSCREENSTATE: {
+ CVarList2 vars(std::string{effect}, 0, 's');
+ try {
+ static_.fullscreenStateInternal = std::stoi(std::string{vars[0]});
+ if (!vars[1].empty())
+ static_.fullscreenStateClient = std::stoi(std::string{vars[1]});
+ } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyStaticRule: invalid fullscreen state {}", effect); }
+ break;
+ }
+ case WINDOW_RULE_EFFECT_MOVE: {
+ static_.position = effect;
+ break;
+ }
+ case WINDOW_RULE_EFFECT_SIZE: {
+ static_.size = effect;
+ break;
+ }
+ case WINDOW_RULE_EFFECT_CENTER: {
+ static_.center = truthy(effect);
+ break;
+ }
+ case WINDOW_RULE_EFFECT_PSEUDO: {
+ static_.pseudo = truthy(effect);
+ break;
+ }
+ case WINDOW_RULE_EFFECT_MONITOR: {
+ static_.monitor = effect;
+ break;
+ }
+ case WINDOW_RULE_EFFECT_WORKSPACE: {
+ static_.workspace = effect;
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NOINITIALFOCUS: {
+ static_.noInitialFocus = truthy(effect);
+ break;
+ }
+ case WINDOW_RULE_EFFECT_PIN: {
+ static_.pin = truthy(effect);
+ break;
+ }
+ case WINDOW_RULE_EFFECT_GROUP: {
+ static_.group = effect;
+ break;
+ }
+ case WINDOW_RULE_EFFECT_SUPPRESSEVENT: {
+ CVarList2 varlist(std::string{effect}, 0, 's');
+ for (const auto& e : varlist) {
+ static_.suppressEvent.emplace_back(e);
+ }
+ break;
+ }
+ case WINDOW_RULE_EFFECT_CONTENT: {
+ static_.content = NContentType::fromString(effect);
+ break;
+ }
+ case WINDOW_RULE_EFFECT_NOCLOSEFOR: {
+ try {
+ static_.noCloseFor = std::stoi(effect);
+ } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyStaticRule: invalid no close for {}", effect); }
+ break;
+ }
+ }
+ }
+
+ return SRuleResult{};
+}
+
+void CWindowRuleApplicator::readStaticRules() {
+ if (!m_window)
+ return;
+
+ static_ = {};
+
+ std::vector<SP<IRule>> toRemove;
+ bool tagsWereChanged = false;
+
+ for (const auto& r : ruleEngine()->rules()) {
+ if (r->type() != RULE_TYPE_WINDOW)
+ continue;
+
+ auto wr = reinterpretPointerCast<CWindowRule>(r);
+
+ if (!wr->matches(m_window.lock(), true))
+ continue;
+
+ applyStaticRule(wr);
+ const auto RES = applyDynamicRule(wr); // also apply dynamic, because we won't recheck it before layout gets data
+ tagsWereChanged = tagsWereChanged || RES.tagsChanged;
+
+ if (wr->isExecRule())
+ toRemove.emplace_back(wr);
+ }
+
+ for (const auto& wr : toRemove) {
+ ruleEngine()->unregisterRule(wr);
+ }
+
+ // recheck some props people might wanna use for static rules.
+ std::underlying_type_t<eRuleProperty> propsToRecheck = RULE_PROP_NONE;
+ if (tagsWereChanged)
+ propsToRecheck |= RULE_PROP_TAG;
+ if (static_.content != NContentType::CONTENT_TYPE_NONE)
+ propsToRecheck |= RULE_PROP_CONTENT;
+
+ if (propsToRecheck != RULE_PROP_NONE) {
+ for (const auto& r : ruleEngine()->rules()) {
+ if (r->type() != RULE_TYPE_WINDOW)
+ continue;
+
+ if (!(r->getPropertiesMask() & propsToRecheck))
+ continue;
+
+ auto wr = reinterpretPointerCast<CWindowRule>(r);
+
+ if (!wr->matches(m_window.lock(), true))
+ continue;
+
+ applyStaticRule(wr);
+ }
+ }
+}
+
+void CWindowRuleApplicator::propertiesChanged(std::underlying_type_t<eRuleProperty> props) {
+ if (!m_window || !m_window->m_isMapped || m_window->isHidden())
+ return;
+
+ resetProps(props);
+
+ bool needsRelayout = false;
+
+ std::unordered_set<CWindowRuleEffectContainer::storageType> effectsNeedingRecheck;
+ std::unordered_set<SP<CWindowRule>> passedWrs;
+
+ for (const auto& r : ruleEngine()->rules()) {
+ if (r->type() != RULE_TYPE_WINDOW)
+ continue;
+
+ if (!(r->getPropertiesMask() & props))
+ continue;
+
+ auto wr = reinterpretPointerCast<CWindowRule>(r);
+
+ if (!wr->matches(m_window.lock()))
+ continue;
+
+ for (const auto& [type, eff] : wr->effects()) {
+ effectsNeedingRecheck.emplace(type);
+ }
+
+ passedWrs.emplace(std::move(wr));
+ }
+
+ for (const auto& r : ruleEngine()->rules()) {
+ if (r->type() != RULE_TYPE_WINDOW)
+ continue;
+
+ const auto WR = reinterpretPointerCast<CWindowRule>(r);
+
+ if (!(WR->getPropertiesMask() & props) && !setsIntersect(WR->effectsSet(), effectsNeedingRecheck))
+ continue;
+
+ if (!std::ranges::contains(passedWrs, WR) && !WR->matches(m_window.lock()))
+ continue;
+
+ const auto RES = applyDynamicRule(WR);
+ needsRelayout = needsRelayout || RES.needsRelayout;
+ }
+
+ m_window->updateDecorationValues();
+
+ if (needsRelayout)
+ g_pDecorationPositioner->forceRecalcFor(m_window.lock());
+}