From 94bd4123c6e8fd3162e245b0d568c9d6dfd75780 Mon Sep 17 00:00:00 2001 From: ItsOhen Date: Sun, 15 Mar 2026 20:32:05 +0100 Subject: [PATCH] desktop/rules: fix static rules and content type. (#13725) Hopefully for the last time in a while. Also a lot easier to add in new static types with the recheck done in window map instead of the rule. --- hyprtester/src/tests/main/window.cpp | 35 ++++++++------- .../rule/windowRule/WindowRuleApplicator.cpp | 43 ++++++++++--------- .../rule/windowRule/WindowRuleApplicator.hpp | 6 ++- src/desktop/view/Window.cpp | 14 ++++-- src/protocols/types/ContentType.cpp | 12 +++++- 5 files changed, 66 insertions(+), 44 deletions(-) diff --git a/hyprtester/src/tests/main/window.cpp b/hyprtester/src/tests/main/window.cpp index a42ffd38d..5c0fe2fe2 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -647,31 +647,36 @@ static bool testWindowRuleWorkspaceEmpty() { return true; } -static void testContentRules() { +static bool testContentRules() { NLog::log("{}Testing content window rules", Colors::YELLOW); // kill me PLEASE - OK(getFromSocket("/keyword windowrule match:class kitty_bitch, content game")); + OK(getFromSocket("/keyword windowrule match:class kitty_content_string, content game")); + OK(getFromSocket("/keyword windowrule match:class kitty_content_numbers, content 3")); OK(getFromSocket("/keyword windowrule match:content game, border_size 10")); OK(getFromSocket("/keyword windowrule match:content 3, opacity 0.5")); - getFromSocket("/dispatch workspace 420"); + const auto testProps = []() { + EXPECT_CONTAINS(getFromSocket("/getprop active border_size"), "10"); + EXPECT_CONTAINS(getFromSocket("/getprop active opacity"), "0.5"); + }; + if (!spawnKitty("kitty_content_string")) + return false; + waitForActiveWindow("kitty_content_string"); + testProps(); - if (!spawnKitty("kitty_bitch")) { - NLog::log("{}Error: failed to spawn kitty", Colors::RED); - return; - } + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); - { - auto res = getFromSocket("/getprop active border_size"); - EXPECT_CONTAINS(res, "10"); - } + if (!spawnKitty("kitty_content_numbers")) + return false; + waitForActiveWindow("kitty_content_numbers"); + testProps(); - { - auto res = getFromSocket("/getprop active opacity"); - EXPECT_CONTAINS(res, "0.5"); - } + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + return true; } static bool test() { diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp index 07cb5f644..dd0e7fb87 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp @@ -547,9 +547,10 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const return SRuleResult{}; } -void CWindowRuleApplicator::readStaticRules(bool preRead) { +// +bool CWindowRuleApplicator::readStaticRules(bool preRead) { if (!m_window) - return; + return false; static_ = {}; @@ -575,36 +576,36 @@ void CWindowRuleApplicator::readStaticRules(bool preRead) { tagsWereChanged = tagsWereChanged || RES.tagsChanged; } - // recheck some props people might wanna use for static rules. - std::underlying_type_t propsToRecheck = RULE_PROP_NONE; + // set a recheck for some props people might wanna use for static rules. 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(r); - - if (!wr->matches(m_window.lock(), true)) - continue; - - applyStaticRule(wr); - } - } - for (const auto& wr : execRules) { applyStaticRule(wr); applyDynamicRule(wr); if (!preRead) ruleEngine()->unregisterRule(wr); } + return (propsToRecheck != RULE_PROP_NONE); +} + +void CWindowRuleApplicator::recheckStaticRules() { + for (const auto& r : ruleEngine()->rules()) { + if (r->type() != RULE_TYPE_WINDOW) + continue; + + if (!(r->getPropertiesMask() & propsToRecheck)) + continue; + + auto wr = reinterpretPointerCast(r); + + if (!wr->matches(m_window.lock(), true)) + continue; + + applyStaticRule(wr); + } } void CWindowRuleApplicator::propertiesChanged(std::underlying_type_t props) { diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp index 5c1d4fd17..8bcf70087 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp @@ -33,7 +33,8 @@ namespace Desktop::Rule { void propertiesChanged(std::underlying_type_t props); std::unordered_set resetProps(std::underlying_type_t props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); - void readStaticRules(bool preRead = false); + bool readStaticRules(bool preRead = false); + void recheckStaticRules(); // static props struct { @@ -138,7 +139,8 @@ namespace Desktop::Rule { #undef DEFINE_PROP private: - PHLWINDOWREF m_window; + PHLWINDOWREF m_window; + std::underlying_type_t propsToRecheck = RULE_PROP_NONE; struct SRuleResult { bool needsRelayout = false; diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp index 4570a9012..00d32a475 100644 --- a/src/desktop/view/Window.cpp +++ b/src/desktop/view/Window.cpp @@ -1719,8 +1719,7 @@ void CWindow::mapWindow() { requestedClientFSMode = FSMODE_FULLSCREEN; MONITORID requestedFSMonitor = m_wantsInitialFullscreenMonitor; - m_ruleApplicator->readStaticRules(); - { + auto setStaticProps = [&]() { if (!m_ruleApplicator->static_.monitor.empty()) { const auto& MONITORSTR = m_ruleApplicator->static_.monitor; if (MONITORSTR == "unset") @@ -1771,8 +1770,8 @@ void CWindow::mapWindow() { if (m_ruleApplicator->static_.fullscreenStateClient || m_ruleApplicator->static_.fullscreenStateInternal) { requestedFSState = Desktop::View::SFullscreenState{ - .internal = sc(m_ruleApplicator->static_.fullscreenStateInternal.value_or(0)), - .client = sc(m_ruleApplicator->static_.fullscreenStateClient.value_or(0)), + .internal = sc(m_ruleApplicator->static_.fullscreenStateInternal.value_or(0)), + .client = sc(m_ruleApplicator->static_.fullscreenStateClient.value_or(0)), }; } @@ -1846,6 +1845,13 @@ void CWindow::mapWindow() { if (m_ruleApplicator->static_.noCloseFor) m_closeableSince = Time::steadyNow() + std::chrono::milliseconds(m_ruleApplicator->static_.noCloseFor.value()); + }; + + const bool recheck = m_ruleApplicator->readStaticRules(); + setStaticProps(); + if (recheck) { + m_ruleApplicator->recheckStaticRules(); + setStaticProps(); } // make it uncloseable if it's a Hyprland dialog diff --git a/src/protocols/types/ContentType.cpp b/src/protocols/types/ContentType.cpp index b5b0041c5..e2e58f3e5 100644 --- a/src/protocols/types/ContentType.cpp +++ b/src/protocols/types/ContentType.cpp @@ -1,6 +1,7 @@ #include "ContentType.hpp" +#include "debug/log/Logger.hpp" +#include #include -#include #include namespace NContentType { @@ -8,6 +9,13 @@ namespace NContentType { {"none", CONTENT_TYPE_NONE}, {"photo", CONTENT_TYPE_PHOTO}, {"video", CONTENT_TYPE_VIDEO}, {"game", CONTENT_TYPE_GAME}}; eContentType fromString(const std::string name) { + if (Hyprutils::String::isNumber(name)) { + try { + auto n = std::stoi(name); + if (n >= 0 && n <= 3) + return sc(n); + } catch (std::exception& e) { Log::logger->log(Log::ERR, "NContentType::fromString: invalid number {}, need to be between 0 and 3", name); } + } auto it = table.find(name); if (it != table.end()) return it->second; @@ -42,4 +50,4 @@ namespace NContentType { default: return DRM_MODE_CONTENT_TYPE_NO_DATA; } } -} \ No newline at end of file +}