diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 3ef6917cce99..acdd59e3b8db 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -1062,16 +1062,16 @@ std::vector CConfigManager::getMatchingRules(PHLWINDOW pWindow, boo bool hasFloating = pWindow->m_bIsFloating; bool hasFullscreen = pWindow->m_bIsFullscreen; + // local tags for dynamic tag rule match + auto tags = pWindow->m_tags; + for (auto& rule : m_dWindowRules) { // check if we have a matching rule if (!rule.v2) { try { - if (rule.szValue.starts_with("tag:")) { - const auto tag = rule.szValue.substr(4); - - if (!pWindow->isTagged(tag)) - continue; - } else if (rule.szValue.starts_with("title:")) { + if (rule.szValue.starts_with("tag:") && !tags.isTagged(rule.szValue.substr(4))) + continue; + else if (rule.szValue.starts_with("title:")) { std::regex RULECHECK(rule.szValue.substr(6)); if (!std::regex_search(title, RULECHECK)) @@ -1088,10 +1088,8 @@ std::vector CConfigManager::getMatchingRules(PHLWINDOW pWindow, boo } } else { try { - if (!rule.szTag.empty()) { - if (!pWindow->isTagged(rule.szTag)) - continue; - } + if (!rule.szTag.empty() && !tags.isTagged(rule.szTag)) + continue; if (!rule.szClass.empty()) { std::regex RULECHECK(rule.szClass); @@ -1183,6 +1181,13 @@ std::vector CConfigManager::getMatchingRules(PHLWINDOW pWindow, boo returns.push_back(rule); + // apply tag with local tags + if (rule.szRule.starts_with("tag")) { + CVarList vars{rule.szRule, 0, 's', true}; + if (vars.size() == 2 && vars[0] == "tag") + tags.applyTag(vars[1]); + } + if (dynamic) continue; @@ -2044,19 +2049,19 @@ bool windowRuleValid(const std::string& RULE) { "keepaspectratio", "maximize", "nearestneighbor", "noanim", "noblur", "noborder", "nodim", "nofocus", "noinitialfocus", "nomaxsize", "noshadow", "opaque", "pin", "stayfocused", "tile", "windowdance", }; - static const auto rules_prefix = std::vector{ + static const auto rulesPrefix = std::vector{ "animation", "bordercolor", "bordersize", "center", "group", "idleinhibit", "maxsize", "minsize", "monitor", "move", "opacity", "plugin:", "pseudo", "rounding", "size", "suppressevent", "tag", "workspace", "xray", }; - return rules.contains(RULE) || std::any_of(rules_prefix.begin(), rules_prefix.end(), [&RULE](auto prefix) { return RULE.starts_with(prefix); }); + return rules.contains(RULE) || std::any_of(rulesPrefix.begin(), rulesPrefix.end(), [&RULE](auto prefix) { return RULE.starts_with(prefix); }); } bool layerRuleValid(const std::string& RULE) { - static const auto rules = std::unordered_set{"noanim", "blur", "blurpopups", "dimaround"}; - static const auto rules_prefix = std::vector{"ignorealpha", "ignorezero", "xray", "animation"}; + static const auto rules = std::unordered_set{"noanim", "blur", "blurpopups", "dimaround"}; + static const auto rulesPrefix = std::vector{"ignorealpha", "ignorezero", "xray", "animation"}; - return rules.contains(RULE) || std::any_of(rules_prefix.begin(), rules_prefix.end(), [&RULE](auto prefix) { return RULE.starts_with(prefix); }); + return rules.contains(RULE) || std::any_of(rulesPrefix.begin(), rulesPrefix.end(), [&RULE](auto prefix) { return RULE.starts_with(prefix); }); } std::optional CConfigManager::handleWindowRule(const std::string& command, const std::string& value) { diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index b87357a5cdbd..8f911f836c16 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -147,13 +147,13 @@ std::string monitorsRequest(eHyprCtlOutputFormat format, std::string request) { } static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) { - const bool isJson = format == eHyprCtlOutputFormat::FORMAT_JSON; + const auto tags = w->m_tags.getTags(); - if (isJson) - return std::accumulate(w->m_tags.begin(), w->m_tags.end(), std::string(), + if (format == eHyprCtlOutputFormat::FORMAT_JSON) + return std::accumulate(tags.begin(), tags.end(), std::string(), [](const std::string& a, const std::string& b) { return a.empty() ? std::format("\"{}\"", b) : std::format("{}, \"{}\"", a, b); }); else - return std::accumulate(w->m_tags.begin(), w->m_tags.end(), std::string(), [](const std::string& a, const std::string& b) { return a.empty() ? b : a + ", " + b; }); + return std::accumulate(tags.begin(), tags.end(), std::string(), [](const std::string& a, const std::string& b) { return a.empty() ? b : a + ", " + b; }); } static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) { diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp index 27f42ca5db76..451d562e7642 100644 --- a/src/desktop/Window.cpp +++ b/src/desktop/Window.cpp @@ -615,9 +615,9 @@ void CWindow::applyDynamicRule(const SWindowRule& r) { CVarList vars{r.szRule, 0, 's', true}; if (vars.size() == 2 && vars[0] == "tag") - applyTag(vars[1], true); + m_tags.applyTag(vars[1], true); else - Debug::log(ERR, "Rule tag invalid: {}", r.szRule); + Debug::log(ERR, "Tag rule invalid: {}", r.szRule); } else if (r.szRule.starts_with("rounding")) { try { m_sAdditionalConfigData.rounding = std::stoi(r.szRule.substr(r.szRule.find_first_of(' ') + 1)); @@ -799,7 +799,7 @@ void CWindow::updateDynamicRules() { m_sAdditionalConfigData.nearestNeighbor = false; m_eIdleInhibitMode = IDLEINHIBIT_NONE; - std::erase_if(m_tags, [](const auto& tag) { return tag.ends_with("*"); }); + m_tags.removeDynamicTags(); m_vMatchedRules = g_pConfigManager->getMatchingRules(m_pSelf.lock()); for (auto& r : m_vMatchedRules) { @@ -851,59 +851,6 @@ bool CWindow::hasPopupAt(const Vector2D& pos) { return popup && popup->m_sWLSurface.wlr(); } -bool CWindow::isTagged(const std::string& tag, bool strict) { - return m_tags.contains(tag) || (!strict && m_tags.contains(tag + "*")); -} - -void CWindow::applyTag(const std::string& tag, bool dynamic) { - - std::string tag_real = dynamic ? tag + "*" : tag; - - bool changed = true; - bool setTag = true; - - if (tag_real.starts_with("-")) { // unset - tag_real = tag_real.substr(1); - changed = isTagged(tag_real, true); - setTag = false; - } else if (tag_real.starts_with("+")) { // set - tag_real = tag_real.substr(1); - changed = !isTagged(tag_real, true); - } else // toggle if without prefix - setTag = !isTagged(tag_real, true); - - if (!changed) - return; - - if (setTag) - m_tags.emplace(tag_real); - else - m_tags.erase(tag_real); - - // re run tag rules after update tag - queueScanForTagMatch(); -} - -void CWindow::queueScanForTagMatch() { - // TODO: assume single thread for window operation - if (!m_tagScanSource) - m_tagScanSource = wl_event_loop_add_idle( - g_pCompositor->m_sWLEventLoop, - [](void* data) { - auto w = static_cast(data); - - w->m_vMatchedRules = g_pConfigManager->getMatchingRules(w->m_pSelf.lock()); - for (auto& r : w->m_vMatchedRules) - if ((r.v2 && !r.szTag.empty() && w->isTagged(r.szTag)) || (!r.v2 && r.szValue.starts_with("tag:") && w->isTagged(r.szValue.substr(4)))) - w->applyDynamicRule(r); - - // NOTE: if tag from tag, then the nested tag's rescan will be ignored - wl_event_source_remove(w->m_tagScanSource); - w->m_tagScanSource = nullptr; - }, - this); -} - void CWindow::applyGroupRules() { if ((m_eGroupRules & GROUP_SET && m_bFirstMap) || m_eGroupRules & GROUP_SET_ALWAYS) createGroup(); @@ -1424,6 +1371,7 @@ void CWindow::onUpdateState() { void CWindow::onUpdateMeta() { const auto NEWTITLE = fetchTitle(); + bool doUpdate = false; if (m_szTitle != NEWTITLE) { m_szTitle = NEWTITLE; @@ -1436,11 +1384,8 @@ void CWindow::onUpdateMeta() { EMIT_HOOK_EVENT("activeWindow", m_pSelf.lock()); } - updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(m_pSelf.lock()); - updateToplevel(); - Debug::log(LOG, "Window {:x} set title to {}", (uintptr_t)this, m_szTitle); + doUpdate = true; } const auto NEWCLASS = fetchClass(); @@ -1453,11 +1398,14 @@ void CWindow::onUpdateMeta() { EMIT_HOOK_EVENT("activeWindow", m_pSelf.lock()); } + Debug::log(LOG, "Window {:x} set class to {}", (uintptr_t)this, m_szClass); + doUpdate = true; + } + + if (doUpdate) { updateDynamicRules(); g_pCompositor->updateWindowAnimatedDecorationValues(m_pSelf.lock()); updateToplevel(); - - Debug::log(LOG, "Window {:x} set class to {}", (uintptr_t)this, m_szClass); } } diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp index 66393dd7d14e..b354b13d3c57 100644 --- a/src/desktop/Window.hpp +++ b/src/desktop/Window.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include "../config/ConfigDataValues.hpp" @@ -9,6 +8,7 @@ #include "../helpers/AnimatedVariable.hpp" #include "../helpers/Vector2D.hpp" #include "../helpers/signal/Signal.hpp" +#include "../helpers/TagKeeper.hpp" #include "../macros.hpp" #include "../managers/XWaylandManager.hpp" #include "../render/decorations/IHyprWindowDecoration.hpp" @@ -394,8 +394,7 @@ class CWindow { std::vector m_vMatchedRules; // window tags - std::set m_tags; - wl_event_source* m_tagScanSource = nullptr; + TagKeeper m_tags; // For the list lookup bool operator==(const CWindow& rhs) { @@ -440,10 +439,6 @@ class CWindow { void activate(bool force = false); int surfacesCount(); - bool isTagged(const std::string& tag, bool strict = false); - void applyTag(const std::string& tag, bool dynamic = false); - void queueScanForTagMatch(); - int getRealBorderSize(); void updateSpecialRenderData(); void updateSpecialRenderData(const struct SWorkspaceRule&); diff --git a/src/helpers/TagKeeper.cpp b/src/helpers/TagKeeper.cpp new file mode 100644 index 000000000000..55b194efc794 --- /dev/null +++ b/src/helpers/TagKeeper.cpp @@ -0,0 +1,40 @@ +#include "TagKeeper.hpp" + +bool TagKeeper::isTagged(const std::string& tag, bool strict) { + return m_tags.contains(tag) || (!strict && m_tags.contains(tag + "*")); +} + +bool TagKeeper::applyTag(const std::string& tag, bool dynamic) { + + std::string tagReal = tag; + + if (dynamic && !tag.ends_with("*")) + tagReal += "*"; + + bool changed = true; + bool setTag = true; + + if (tagReal.starts_with("-")) { // unset + tagReal = tagReal.substr(1); + changed = isTagged(tagReal, true); + setTag = false; + } else if (tagReal.starts_with("+")) { // set + tagReal = tagReal.substr(1); + changed = !isTagged(tagReal, true); + } else // toggle if without prefix + setTag = !isTagged(tagReal, true); + + if (!changed) + return false; + + if (setTag) + m_tags.emplace(tagReal); + else + m_tags.erase(tagReal); + + return true; +} + +bool TagKeeper::removeDynamicTags() { + return std::erase_if(m_tags, [](const auto& tag) { return tag.ends_with("*"); }); +} diff --git a/src/helpers/TagKeeper.hpp b/src/helpers/TagKeeper.hpp new file mode 100644 index 000000000000..181e497a90fc --- /dev/null +++ b/src/helpers/TagKeeper.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +class TagKeeper { + private: + std::set m_tags; + + public: + bool isTagged(const std::string& tag, bool strict = false); + bool applyTag(const std::string& tag, bool dynamic = false); + bool removeDynamicTags(); + + inline const auto& getTags() { + return m_tags; + }; +}; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 71173b63833c..4f89315d6688 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -1,18 +1,16 @@ -#include "KeybindManager.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" -#include "debug/Log.hpp" -#include "helpers/VarList.hpp" #include "../config/ConfigValue.hpp" -#include "TokenManager.hpp" -#include "../protocols/ShortcutsInhibit.hpp" #include "../devices/IKeyboard.hpp" #include "../managers/SeatManager.hpp" +#include "../protocols/ShortcutsInhibit.hpp" +#include "../render/decorations/CHyprGroupBarDecoration.hpp" +#include "KeybindManager.hpp" +#include "TokenManager.hpp" +#include "debug/Log.hpp" +#include "helpers/VarList.hpp" #include -#include #include #include -#include #include #include @@ -1941,8 +1939,10 @@ void CKeybindManager::tagWindow(std::string args) { else return; - if (PWINDOW) - PWINDOW->applyTag(vars[0]); + if (PWINDOW && PWINDOW->m_tags.applyTag(vars[0])) { + PWINDOW->updateDynamicRules(); + g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW->m_pSelf.lock()); + } } void CKeybindManager::setSubmap(std::string submap) {