windowrules: rewrite completely (#12269)

Reworks the window rule syntax completely

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
This commit is contained in:
Vaxry 2025-11-17 18:34:02 +00:00 committed by GitHub
parent 95ee08b340
commit c2670e9ab9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
93 changed files with 3574 additions and 2255 deletions

View file

@ -8,12 +8,15 @@
#include "../render/decorations/CHyprGroupBarDecoration.hpp"
#include "config/ConfigDataValues.hpp"
#include "config/ConfigValue.hpp"
#include "../desktop/WindowRule.hpp"
#include "../protocols/LayerShell.hpp"
#include "../xwayland/XWayland.hpp"
#include "../protocols/OutputManagement.hpp"
#include "../managers/animation/AnimationManager.hpp"
#include "../desktop/LayerSurface.hpp"
#include "../desktop/rule/Engine.hpp"
#include "../desktop/rule/windowRule/WindowRule.hpp"
#include "../desktop/rule/layerRule/LayerRule.hpp"
#include "../debug/HyprCtl.hpp"
#include "defaultConfig.hpp"
#include "../render/Renderer.hpp"
@ -299,54 +302,6 @@ static Hyprlang::CParseResult handleUnbind(const char* c, const char* v) {
return result;
}
static Hyprlang::CParseResult handleWindowRule(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleWindowRule(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult handleLayerRule(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleLayerRule(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult handleWindowRuleV2(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleWindowRule(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult handleBlurLS(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleBlurLS(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult handleWorkspaceRules(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
@ -431,6 +386,30 @@ static Hyprlang::CParseResult handleGesture(const char* c, const char* v) {
return result;
}
static Hyprlang::CParseResult handleWindowrule(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleWindowrule(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult handleLayerrule(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleLayerrule(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
void CConfigManager::registerConfigVar(const char* name, const Hyprlang::INT& val) {
m_configValueNumber++;
m_config->addConfigValue(name, val);
@ -463,7 +442,6 @@ CConfigManager::CConfigManager() {
m_config = makeUnique<Hyprlang::CConfig>(m_configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true});
registerConfigVar("general:border_size", Hyprlang::INT{1});
registerConfigVar("general:no_border_on_floating", Hyprlang::INT{0});
registerConfigVar("general:gaps_in", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "5"});
registerConfigVar("general:gaps_out", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "20"});
registerConfigVar("general:float_gaps", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "0"});
@ -858,6 +836,16 @@ CConfigManager::CConfigManager() {
m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1});
m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1});
// windowrule v3
m_config->addSpecialCategory("windowrule", {.key = "name"});
m_config->addSpecialConfigValue("windowrule", "enable", Hyprlang::INT{1});
// layerrule v2
m_config->addSpecialCategory("layerrule", {.key = "name"});
m_config->addSpecialConfigValue("layerrule", "enable", Hyprlang::INT{1});
reloadRuleConfigs();
// keywords
m_config->registerHandler(&::handleExec, "exec", {false});
m_config->registerHandler(&::handleRawExec, "execr", {false});
@ -868,14 +856,12 @@ CConfigManager::CConfigManager() {
m_config->registerHandler(&::handleBind, "bind", {true});
m_config->registerHandler(&::handleUnbind, "unbind", {false});
m_config->registerHandler(&::handleWorkspaceRules, "workspace", {false});
m_config->registerHandler(&::handleWindowRule, "windowrule", {false});
m_config->registerHandler(&::handleLayerRule, "layerrule", {false});
m_config->registerHandler(&::handleWindowRuleV2, "windowrulev2", {false});
m_config->registerHandler(&::handleWindowrule, "windowrule", {false});
m_config->registerHandler(&::handleLayerrule, "layerrule", {false});
m_config->registerHandler(&::handleBezier, "bezier", {false});
m_config->registerHandler(&::handleAnimation, "animation", {false});
m_config->registerHandler(&::handleSource, "source", {false});
m_config->registerHandler(&::handleSubmap, "submap", {false});
m_config->registerHandler(&::handleBlurLS, "blurls", {false});
m_config->registerHandler(&::handlePlugin, "plugin", {false});
m_config->registerHandler(&::handlePermission, "permission", {false});
m_config->registerHandler(&::handleGesture, "gesture", {false});
@ -905,6 +891,26 @@ CConfigManager::CConfigManager() {
g_pEventLoopManager->doLater([ERR] { g_pHyprError->queueCreate(ERR.value(), CHyprColor{1.0, 0.1, 0.1, 1.0}); });
}
void CConfigManager::reloadRuleConfigs() {
// FIXME: this should also remove old values if they are removed
for (const auto& r : Desktop::Rule::allMatchPropStrings()) {
m_config->addSpecialConfigValue("windowrule", ("match:" + r).c_str(), Hyprlang::STRING{""});
}
for (const auto& r : Desktop::Rule::windowEffects()->allEffectStrings()) {
m_config->addSpecialConfigValue("windowrule", r.c_str(), Hyprlang::STRING{""});
}
for (const auto& r : Desktop::Rule::allMatchPropStrings()) {
m_config->addSpecialConfigValue("layerrule", ("match:" + r).c_str(), Hyprlang::STRING{""});
}
for (const auto& r : Desktop::Rule::layerEffects()->allEffectStrings()) {
m_config->addSpecialConfigValue("layerrule", r.c_str(), Hyprlang::STRING{""});
}
}
std::optional<std::string> CConfigManager::generateConfig(std::string configPath) {
std::string parentPath = std::filesystem::path(configPath).parent_path();
@ -991,6 +997,7 @@ void CConfigManager::reload() {
m_configCurrentPath = getMainConfigPath();
const auto ERR = m_config->parse();
const auto monitorError = handleMonitorv2();
const auto ruleError = reloadRules();
m_lastConfigVerificationWasSuccessful = !ERR.error && !monitorError.error;
postConfigReload(ERR.error || !monitorError.error ? ERR : monitorError);
}
@ -1058,20 +1065,18 @@ void CConfigManager::setDefaultAnimationVars() {
std::optional<std::string> CConfigManager::resetHLConfig() {
m_monitorRules.clear();
m_windowRules.clear();
g_pKeybindManager->clearKeybinds();
g_pAnimationManager->removeAllBeziers();
g_pAnimationManager->addBezierWithName("linear", Vector2D(0.0, 0.0), Vector2D(1.0, 1.0));
g_pTrackpadGestures->clearGestures();
m_mAdditionalReservedAreas.clear();
m_blurLSNamespaces.clear();
m_workspaceRules.clear();
setDefaultAnimationVars(); // reset anims
m_declaredPlugins.clear();
m_layerRules.clear();
m_failedPluginConfigValues.clear();
m_finalExecRequests.clear();
m_keywordRules.clear();
// paths
m_configPaths.clear();
@ -1081,6 +1086,8 @@ std::optional<std::string> CConfigManager::resetHLConfig() {
const auto RET = verifyConfigExists();
reloadRuleConfigs();
return RET;
}
@ -1179,6 +1186,77 @@ Hyprlang::CParseResult CConfigManager::handleMonitorv2() {
return result;
}
std::optional<std::string> CConfigManager::addRuleFromConfigKey(const std::string& name) {
const auto ENABLED = m_config->getSpecialConfigValuePtr("windowrule", "enable", name.c_str());
if (ENABLED && ENABLED->m_bSetByUser && std::any_cast<Hyprlang::INT>(ENABLED->getValue()) == 0)
return std::nullopt;
SP<Desktop::Rule::CWindowRule> rule = makeShared<Desktop::Rule::CWindowRule>(name);
for (const auto& r : Desktop::Rule::allMatchPropStrings()) {
auto VAL = m_config->getSpecialConfigValuePtr("windowrule", ("match:" + r).c_str(), name.c_str());
if (VAL && VAL->m_bSetByUser)
rule->registerMatch(Desktop::Rule::matchPropFromString(r).value_or(Desktop::Rule::RULE_PROP_NONE), std::any_cast<Hyprlang::STRING>(VAL->getValue()));
}
for (const auto& e : Desktop::Rule::windowEffects()->allEffectStrings()) {
auto VAL = m_config->getSpecialConfigValuePtr("windowrule", e.c_str(), name.c_str());
if (VAL && VAL->m_bSetByUser)
rule->addEffect(Desktop::Rule::windowEffects()->get(e).value_or(Desktop::Rule::WINDOW_RULE_EFFECT_NONE), std::any_cast<Hyprlang::STRING>(VAL->getValue()));
}
Desktop::Rule::ruleEngine()->registerRule(std::move(rule));
return std::nullopt;
}
std::optional<std::string> CConfigManager::addLayerRuleFromConfigKey(const std::string& name) {
const auto ENABLED = m_config->getSpecialConfigValuePtr("layerrule", "enable", name.c_str());
if (ENABLED && ENABLED->m_bSetByUser && std::any_cast<Hyprlang::INT>(ENABLED->getValue()) != 0)
return std::nullopt;
SP<Desktop::Rule::CLayerRule> rule = makeShared<Desktop::Rule::CLayerRule>(name);
for (const auto& r : Desktop::Rule::allMatchPropStrings()) {
auto VAL = m_config->getSpecialConfigValuePtr("layerrule", ("match:" + r).c_str(), name.c_str());
if (VAL && VAL->m_bSetByUser)
rule->registerMatch(Desktop::Rule::matchPropFromString(r).value_or(Desktop::Rule::RULE_PROP_NONE), std::any_cast<Hyprlang::STRING>(VAL->getValue()));
}
for (const auto& e : Desktop::Rule::layerEffects()->allEffectStrings()) {
auto VAL = m_config->getSpecialConfigValuePtr("layerrule", e.c_str(), name.c_str());
if (VAL && VAL->m_bSetByUser)
rule->addEffect(Desktop::Rule::layerEffects()->get(e).value_or(Desktop::Rule::LAYER_RULE_EFFECT_NONE), std::any_cast<Hyprlang::STRING>(VAL->getValue()));
}
Desktop::Rule::ruleEngine()->registerRule(std::move(rule));
return std::nullopt;
}
Hyprlang::CParseResult CConfigManager::reloadRules() {
Desktop::Rule::ruleEngine()->clearAllRules();
Hyprlang::CParseResult result;
for (const auto& name : m_config->listKeysForSpecialCategory("windowrule")) {
const auto error = addRuleFromConfigKey(name);
if (error.has_value())
result.setError(error.value().c_str());
}
for (const auto& name : m_config->listKeysForSpecialCategory("layerrule")) {
const auto error = addLayerRuleFromConfigKey(name);
if (error.has_value())
result.setError(error.value().c_str());
}
for (auto& rule : m_keywordRules) {
Desktop::Rule::ruleEngine()->registerRule(SP<Desktop::Rule::IRule>{rule});
}
Desktop::Rule::ruleEngine()->updateAllRules();
return result;
}
void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
updateWatcher();
@ -1504,229 +1582,6 @@ SWorkspaceRule CConfigManager::mergeWorkspaceRules(const SWorkspaceRule& rule1,
return mergedRule;
}
std::vector<SP<CWindowRule>> CConfigManager::getMatchingRules(PHLWINDOW pWindow, bool dynamic, bool shadowExec) {
if (!valid(pWindow))
return std::vector<SP<CWindowRule>>();
// if the window is unmapped, don't process exec rules yet.
shadowExec = shadowExec || !pWindow->m_isMapped;
std::vector<SP<CWindowRule>> returns;
Debug::log(LOG, "Searching for matching rules for {} (title: {})", pWindow->m_class, pWindow->m_title);
// since some rules will be applied later, we need to store some flags
bool hasFloating = pWindow->m_isFloating;
bool hasFullscreen = pWindow->isFullscreen();
bool isGrouped = pWindow->m_groupData.pNextWindow;
// local tags for dynamic tag rule match
auto tags = pWindow->m_tags;
for (auto const& rule : m_windowRules) {
// check if we have a matching rule
if (!rule->m_v2) {
try {
if (rule->m_value.starts_with("tag:") && !tags.isTagged(rule->m_value.substr(4)))
continue;
if (rule->m_value.starts_with("title:") && !rule->m_v1Regex.passes(pWindow->m_title))
continue;
if (!rule->m_v1Regex.passes(pWindow->m_class))
continue;
} catch (...) {
Debug::log(ERR, "Regex error at {}", rule->m_value);
continue;
}
} else {
try {
if (rule->m_X11 != -1) {
if (pWindow->m_isX11 != rule->m_X11)
continue;
}
if (rule->m_floating != -1) {
if (hasFloating != rule->m_floating)
continue;
}
if (rule->m_fullscreen != -1) {
if (hasFullscreen != rule->m_fullscreen)
continue;
}
if (rule->m_pinned != -1) {
if (pWindow->m_pinned != rule->m_pinned)
continue;
}
if (rule->m_focus != -1) {
if (rule->m_focus != (g_pCompositor->m_lastWindow.lock() == pWindow))
continue;
}
if (rule->m_group != -1) {
if (rule->m_group != isGrouped)
continue;
}
if (rule->m_modal != -1) {
if (rule->m_modal != pWindow->isModal())
continue;
}
if (!rule->m_fullscreenState.empty()) {
const auto ARGS = CVarList(rule->m_fullscreenState, 2, ' ');
//
std::optional<eFullscreenMode> internalMode, clientMode;
if (ARGS[0] == "*")
internalMode = std::nullopt;
else if (isNumber(ARGS[0]))
internalMode = sc<eFullscreenMode>(std::stoi(ARGS[0]));
else
throw std::runtime_error("szFullscreenState internal mode not valid");
if (ARGS[1] == "*")
clientMode = std::nullopt;
else if (isNumber(ARGS[1]))
clientMode = sc<eFullscreenMode>(std::stoi(ARGS[1]));
else
throw std::runtime_error("szFullscreenState client mode not valid");
if (internalMode.has_value() && pWindow->m_fullscreenState.internal != internalMode)
continue;
if (clientMode.has_value() && pWindow->m_fullscreenState.client != clientMode)
continue;
}
if (!rule->m_onWorkspace.empty()) {
const auto PWORKSPACE = pWindow->m_workspace;
if (!PWORKSPACE || !PWORKSPACE->matchesStaticSelector(rule->m_onWorkspace))
continue;
}
if (!rule->m_contentType.empty()) {
try {
const auto contentType = NContentType::fromString(rule->m_contentType);
if (pWindow->getContentType() != contentType)
continue;
} catch (std::exception& e) { Debug::log(ERR, "Rule \"content:{}\" failed with: {}", rule->m_contentType, e.what()); }
}
if (!rule->m_xdgTag.empty()) {
if (pWindow->xdgTag().value_or("") != rule->m_xdgTag)
continue;
}
if (!rule->m_workspace.empty()) {
const auto PWORKSPACE = pWindow->m_workspace;
if (!PWORKSPACE)
continue;
if (rule->m_workspace.starts_with("name:")) {
if (PWORKSPACE->m_name != rule->m_workspace.substr(5))
continue;
} else {
// number
if (!isNumber(rule->m_workspace))
throw std::runtime_error("szWorkspace not name: or number");
const int64_t ID = std::stoll(rule->m_workspace);
if (PWORKSPACE->m_id != ID)
continue;
}
}
if (!rule->m_tag.empty() && !tags.isTagged(rule->m_tag))
continue;
if (!rule->m_class.empty() && !rule->m_classRegex.passes(pWindow->m_class))
continue;
if (!rule->m_title.empty() && !rule->m_titleRegex.passes(pWindow->m_title))
continue;
if (!rule->m_initialTitle.empty() && !rule->m_initialTitleRegex.passes(pWindow->m_initialTitle))
continue;
if (!rule->m_initialClass.empty() && !rule->m_initialClassRegex.passes(pWindow->m_initialClass))
continue;
} catch (std::exception& e) {
Debug::log(ERR, "Regex error at {} ({})", rule->m_value, e.what());
continue;
}
}
// applies. Read the rule and behave accordingly
Debug::log(LOG, "Window rule {} -> {} matched {}", rule->m_rule, rule->m_value, pWindow);
returns.emplace_back(rule);
// apply tag with local tags
if (rule->m_ruleType == CWindowRule::RULE_TAG) {
CVarList vars{rule->m_rule, 0, 's', true};
if (vars.size() == 2 && vars[0] == "tag")
tags.applyTag(vars[1], true);
}
if (dynamic)
continue;
if (rule->m_rule == "float")
hasFloating = true;
else if (rule->m_rule == "fullscreen")
hasFullscreen = true;
}
std::vector<uint64_t> PIDs = {sc<uint64_t>(pWindow->getPID())};
while (getPPIDof(PIDs.back()) > 10)
PIDs.push_back(getPPIDof(PIDs.back()));
bool anyExecFound = false;
for (auto const& er : m_execRequestedRules) {
if (std::ranges::any_of(PIDs, [&](const auto& pid) { return pid == er.iPid; })) {
returns.emplace_back(makeShared<CWindowRule>(er.szRule, "", false, true));
anyExecFound = true;
}
}
if (anyExecFound && !shadowExec) // remove exec rules to unclog searches in the future, why have the garbage here.
std::erase_if(m_execRequestedRules, [&](const SExecRequestedRule& other) { return std::ranges::any_of(PIDs, [&](const auto& pid) { return pid == other.iPid; }); });
return returns;
}
std::vector<SP<CLayerRule>> CConfigManager::getMatchingRules(PHLLS pLS) {
std::vector<SP<CLayerRule>> returns;
if (!pLS->m_layerSurface || pLS->m_fadingOut)
return returns;
for (auto const& lr : m_layerRules) {
if (lr->m_targetNamespace.starts_with("address:0x")) {
if (std::format("address:0x{:x}", rc<uintptr_t>(pLS.get())) != lr->m_targetNamespace)
continue;
} else if (!lr->m_targetNamespaceRegex.passes(pLS->m_layerSurface->m_layerNamespace))
continue;
// hit
returns.emplace_back(lr);
}
if (shouldBlurLS(pLS->m_layerSurface->m_layerNamespace))
returns.emplace_back(makeShared<CLayerRule>(pLS->m_layerSurface->m_layerNamespace, "blur"));
return returns;
}
void CConfigManager::dispatchExecOnce() {
if (m_firstExecDispatched || m_isFirstLaunch)
return;
@ -1832,16 +1687,6 @@ bool CConfigManager::deviceConfigExists(const std::string& dev) {
return m_config->specialCategoryExistsForKey("device", copy.c_str());
}
bool CConfigManager::shouldBlurLS(const std::string& ns) {
for (auto const& bls : m_blurLSNamespaces) {
if (bls == ns) {
return true;
}
}
return false;
}
void CConfigManager::ensureMonitorStatus() {
for (auto const& rm : g_pCompositor->m_realMonitors) {
if (!rm->m_output || rm->m_isUnsafeFallback)
@ -1895,7 +1740,7 @@ void CConfigManager::ensureVRR(PHLMONITOR pMonitor) {
return; // ???
bool wantVRR = PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN);
if (wantVRR && PWORKSPACE->getFullscreenWindow()->m_windowData.noVRR.valueOrDefault())
if (wantVRR && PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault())
wantVRR = false;
if (wantVRR && USEVRR == 3) {
@ -1964,10 +1809,6 @@ const std::vector<SWorkspaceRule>& CConfigManager::getAllWorkspaceRules() {
return m_workspaceRules;
}
void CConfigManager::addExecRule(const SExecRequestedRule& rule) {
m_execRequestedRules.push_back(rule);
}
void CConfigManager::handlePluginLoads() {
if (!g_pPluginSystem)
return;
@ -2676,239 +2517,6 @@ std::optional<std::string> CConfigManager::handleUnbind(const std::string& comma
return {};
}
std::optional<std::string> CConfigManager::handleWindowRule(const std::string& command, const std::string& value) {
const auto VARLIST = CVarList(value, 0, ',', true);
std::vector<std::string_view> tokens;
std::unordered_map<std::string_view, std::string_view> params;
bool parsingParams = false;
for (const auto& varStr : VARLIST) {
std::string_view var = varStr;
auto sep = var.find(':');
std::string_view key = (sep != std::string_view::npos) ? var.substr(0, sep) : var;
bool isParam = (sep != std::string_view::npos && !(key.starts_with("workspace ") || (key.starts_with("monitor ")) || key.ends_with("plugin")));
if (!parsingParams) {
if (!isParam) {
tokens.emplace_back(var);
continue;
}
parsingParams = true;
}
if (sep == std::string_view::npos)
return std::format("Invalid rule: {}, Invalid parameter: {}", value, std::string(var));
auto pos = var.find_first_not_of(' ', sep + 1);
std::string_view val = (pos != std::string_view::npos) ? var.substr(pos) : std::string_view{};
params[key] = val;
}
auto get = [&](std::string_view key) -> std::string_view {
if (auto it = params.find(key); it != params.end())
return it->second;
return {};
};
auto applyParams = [&](SP<CWindowRule> rule) -> bool {
bool set = false;
if (auto v = get("class"); !v.empty()) {
set |= (rule->m_class = v, true);
rule->m_classRegex = {std::string(v)};
}
if (auto v = get("title"); !v.empty()) {
set |= (rule->m_title = v, true);
rule->m_titleRegex = {std::string(v)};
}
if (auto v = get("tag"); !v.empty())
set |= (rule->m_tag = v, true);
if (auto v = get("initialClass"); !v.empty()) {
set |= (rule->m_initialClass = v, true);
rule->m_initialClassRegex = {std::string(v)};
}
if (auto v = get("initialTitle"); !v.empty()) {
set |= (rule->m_initialTitle = v, true);
rule->m_initialTitleRegex = {std::string(v)};
}
if (auto v = get("xwayland"); !v.empty())
set |= (rule->m_X11 = (v == "1"), true);
if (auto v = get("floating"); !v.empty())
set |= (rule->m_floating = (v == "1"), true);
if (auto v = get("fullscreen"); !v.empty())
set |= (rule->m_fullscreen = (v == "1"), true);
if (auto v = get("pinned"); !v.empty())
set |= (rule->m_pinned = (v == "1"), true);
if (auto v = get("focus"); !v.empty())
set |= (rule->m_focus = (v == "1"), true);
if (auto v = get("group"); !v.empty())
set |= (rule->m_group = (v == "1"), true);
if (auto v = get("modal"); !v.empty())
set |= (rule->m_modal = (v == "1"), true);
if (auto v = get("fullscreenstate"); !v.empty())
set |= (rule->m_fullscreenState = v, true);
if (auto v = get("workspace"); !v.empty())
set |= (rule->m_workspace = v, true);
if (auto v = get("onworkspace"); !v.empty())
set |= (rule->m_onWorkspace = v, true);
if (auto v = get("content"); !v.empty())
set |= (rule->m_contentType = v, true);
if (auto v = get("xdgTag"); !v.empty())
set |= (rule->m_xdgTag = v, true);
return set;
};
std::vector<SP<CWindowRule>> rules;
for (auto token : tokens) {
if (token.starts_with("unset")) {
std::string ruleName = "";
if (token.size() <= 6 || token.contains("all"))
ruleName = "all";
else
ruleName = std::string(token.substr(6));
auto rule = makeShared<CWindowRule>(ruleName, value, true);
applyParams(rule);
std::erase_if(m_windowRules, [&](const auto& other) {
if (!other->m_v2)
return other->m_class == rule->m_class && !rule->m_class.empty();
if (rule->m_ruleType != other->m_ruleType && ruleName != "all")
return false;
if (!rule->m_tag.empty() && rule->m_tag != other->m_tag)
return false;
if (!rule->m_class.empty() && rule->m_class != other->m_class)
return false;
if (!rule->m_title.empty() && rule->m_title != other->m_title)
return false;
if (!rule->m_initialClass.empty() && rule->m_initialClass != other->m_initialClass)
return false;
if (!rule->m_initialTitle.empty() && rule->m_initialTitle != other->m_initialTitle)
return false;
if (rule->m_X11 != -1 && rule->m_X11 != other->m_X11)
return false;
if (rule->m_floating != -1 && rule->m_floating != other->m_floating)
return false;
if (rule->m_fullscreen != -1 && rule->m_fullscreen != other->m_fullscreen)
return false;
if (rule->m_pinned != -1 && rule->m_pinned != other->m_pinned)
return false;
if (!rule->m_fullscreenState.empty() && rule->m_fullscreenState != other->m_fullscreenState)
return false;
if (!rule->m_workspace.empty() && rule->m_workspace != other->m_workspace)
return false;
if (rule->m_focus != -1 && rule->m_focus != other->m_focus)
return false;
if (!rule->m_onWorkspace.empty() && rule->m_onWorkspace != other->m_onWorkspace)
return false;
if (!rule->m_contentType.empty() && rule->m_contentType != other->m_contentType)
return false;
if (rule->m_group != -1 && rule->m_group != other->m_group)
return false;
if (rule->m_modal != -1 && rule->m_modal != other->m_modal)
return false;
return true;
});
} else {
auto rule = makeShared<CWindowRule>(std::string(token), value, true);
if (rule->m_ruleType == CWindowRule::RULE_INVALID) {
Debug::log(ERR, "Invalid rule found: {}, Invalid value: {}", value, token);
return std::format("Invalid rule found: {}, Invalid value: {}", value, token);
}
if (applyParams(rule))
rules.emplace_back(rule);
else {
Debug::log(INFO, "===== Skipping rule: {}, Invalid parameters", rule->m_value);
return std::format("Invalid parameters found in: {}", value);
}
}
}
if (rules.empty() && tokens.empty())
return "Invalid rule syntax: no rules provided";
for (auto& rule : rules) {
if (rule->m_ruleType == CWindowRule::RULE_SIZE || rule->m_ruleType == CWindowRule::RULE_MAXSIZE || rule->m_ruleType == CWindowRule::RULE_MINSIZE)
m_windowRules.insert(m_windowRules.begin(), rule);
else
m_windowRules.emplace_back(rule);
}
return {};
}
std::optional<std::string> CConfigManager::handleLayerRule(const std::string& command, const std::string& value) {
const auto RULE = trim(value.substr(0, value.find_first_of(',')));
const auto VALUE = trim(value.substr(value.find_first_of(',') + 1));
// check rule and value
if (RULE.empty() || VALUE.empty())
return "empty rule?";
if (RULE == "unset") {
std::erase_if(m_layerRules, [&](const auto& other) { return other->m_targetNamespace == VALUE; });
return {};
}
auto rule = makeShared<CLayerRule>(RULE, VALUE);
if (rule->m_ruleType == CLayerRule::RULE_INVALID) {
Debug::log(ERR, "Invalid rule found: {}", RULE);
return "Invalid rule found: " + RULE;
}
rule->m_targetNamespaceRegex = {VALUE};
m_layerRules.emplace_back(rule);
for (auto const& m : g_pCompositor->m_monitors)
for (auto const& lsl : m->m_layerSurfaceLayers)
for (auto const& ls : lsl)
ls->applyRules();
return {};
}
void CConfigManager::updateBlurredLS(const std::string& name, const bool forceBlur) {
const bool BYADDRESS = name.starts_with("address:");
std::string matchName = name;
if (BYADDRESS)
matchName = matchName.substr(8);
for (auto const& m : g_pCompositor->m_monitors) {
for (auto const& lsl : m->m_layerSurfaceLayers) {
for (auto const& ls : lsl) {
if (BYADDRESS) {
if (std::format("0x{:x}", rc<uintptr_t>(ls.get())) == matchName)
ls->m_forceBlur = forceBlur;
} else if (ls->m_namespace == matchName)
ls->m_forceBlur = forceBlur;
}
}
}
}
std::optional<std::string> CConfigManager::handleBlurLS(const std::string& command, const std::string& value) {
if (value.starts_with("remove,")) {
const auto TOREMOVE = trim(value.substr(7));
if (std::erase_if(m_blurLSNamespaces, [&](const auto& other) { return other == TOREMOVE; }))
updateBlurredLS(TOREMOVE, false);
return {};
}
m_blurLSNamespaces.emplace_back(value);
updateBlurredLS(value, true);
return {};
}
std::optional<std::string> CConfigManager::handleWorkspaceRules(const std::string& command, const std::string& value) {
// This can either be the monitor or the workspace identifier
const auto FIRST_DELIM = value.find_first_of(',');
@ -3229,6 +2837,82 @@ std::optional<std::string> CConfigManager::handleGesture(const std::string& comm
return std::nullopt;
}
std::optional<std::string> CConfigManager::handleWindowrule(const std::string& command, const std::string& value) {
CVarList2 data(std::string{value}, 0, ',');
SP<Desktop::Rule::CWindowRule> rule = makeShared<Desktop::Rule::CWindowRule>();
const auto& PROPS = Desktop::Rule::allMatchPropStrings();
const auto& EFFECTS = Desktop::Rule::windowEffects()->allEffectStrings();
for (const auto& el : data) {
// split on space, no need for a CVarList here
size_t spacePos = el.find(' ');
if (spacePos == std::string::npos)
return std::format("invalid field {}: missing a value", el);
const bool FIRST_IS_PROP = el.starts_with("match:");
const auto FIRST = FIRST_IS_PROP ? el.substr(6, spacePos - 6) : el.substr(0, spacePos);
if (FIRST_IS_PROP && std::ranges::contains(PROPS, FIRST)) {
// it's a prop
const auto PROP = Desktop::Rule::matchPropFromString(FIRST);
if (!PROP.has_value())
return std::format("invalid prop {}", el);
rule->registerMatch(*PROP, std::string{el.substr(spacePos + 1)});
} else if (!FIRST_IS_PROP && std::ranges::contains(EFFECTS, FIRST)) {
// it's an effect
const auto EFFECT = Desktop::Rule::windowEffects()->get(FIRST);
if (!EFFECT.has_value())
return std::format("invalid effect {}", el);
rule->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)});
} else
return std::format("invalid field type {}", FIRST);
}
m_keywordRules.emplace_back(std::move(rule));
if (g_pHyprCtl && g_pHyprCtl->m_currentRequestParams.isDynamicKeyword)
Desktop::Rule::ruleEngine()->registerRule(SP<Desktop::Rule::IRule>{m_keywordRules.back()});
return std::nullopt;
}
std::optional<std::string> CConfigManager::handleLayerrule(const std::string& command, const std::string& value) {
CVarList2 data(std::string{value}, 0, ',');
SP<Desktop::Rule::CLayerRule> rule = makeShared<Desktop::Rule::CLayerRule>();
const auto& PROPS = Desktop::Rule::allMatchPropStrings();
const auto& EFFECTS = Desktop::Rule::layerEffects()->allEffectStrings();
for (const auto& el : data) {
// split on space, no need for a CVarList here
size_t spacePos = el.find(' ');
if (spacePos == std::string::npos)
return std::format("invalid field {}: missing a value", el);
const bool FIRST_IS_PROP = el.starts_with("match:");
const auto FIRST = FIRST_IS_PROP ? el.substr(6, spacePos - 6) : el.substr(0, spacePos);
if (FIRST_IS_PROP && std::ranges::contains(PROPS, FIRST)) {
// it's a prop
const auto PROP = Desktop::Rule::matchPropFromString(FIRST);
if (!PROP.has_value())
return std::format("invalid prop {}", el);
rule->registerMatch(*PROP, std::string{el.substr(spacePos + 1)});
} else if (!FIRST_IS_PROP && std::ranges::contains(EFFECTS, FIRST)) {
// it's an effect
const auto EFFECT = Desktop::Rule::layerEffects()->get(FIRST);
if (!EFFECT.has_value())
return std::format("invalid effect {}", el);
rule->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)});
} else
return std::format("invalid field type {}", FIRST);
}
m_keywordRules.emplace_back(std::move(rule));
return std::nullopt;
}
const std::vector<SConfigOptionDescription>& CConfigManager::getAllDescriptions() {
return CONFIG_OPTIONS;
}