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

@ -0,0 +1,56 @@
#include "Engine.hpp"
#include "Rule.hpp"
#include "../LayerSurface.hpp"
#include "../../Compositor.hpp"
using namespace Desktop;
using namespace Desktop::Rule;
SP<CRuleEngine> Rule::ruleEngine() {
static SP<CRuleEngine> engine = makeShared<CRuleEngine>();
return engine;
}
void CRuleEngine::registerRule(SP<IRule>&& rule) {
m_rules.emplace_back(std::move(rule));
}
void CRuleEngine::unregisterRule(const std::string& name) {
if (name.empty())
return;
std::erase_if(m_rules, [&name](const auto& el) { return el->name() == name; });
}
void CRuleEngine::unregisterRule(const SP<IRule>& rule) {
std::erase(m_rules, rule);
cleanExecRules();
}
void CRuleEngine::cleanExecRules() {
std::erase_if(m_rules, [](const auto& e) { return e->isExecRule() && e->execExpired(); });
}
void CRuleEngine::updateAllRules() {
cleanExecRules();
for (const auto& w : g_pCompositor->m_windows) {
if (!validMapped(w) || w->isHidden())
continue;
w->m_ruleApplicator->propertiesChanged(RULE_PROP_ALL);
}
for (const auto& ls : g_pCompositor->m_layers) {
if (!validMapped(ls))
continue;
ls->m_ruleApplicator->propertiesChanged(RULE_PROP_ALL);
}
}
void CRuleEngine::clearAllRules() {
std::erase_if(m_rules, [](const auto& e) { return !e->isExecRule() || e->execExpired(); });
}
const std::vector<SP<IRule>>& CRuleEngine::rules() {
return m_rules;
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "Rule.hpp"
namespace Desktop::Rule {
class CRuleEngine {
public:
CRuleEngine() = default;
~CRuleEngine() = default;
void registerRule(SP<IRule>&& rule);
void unregisterRule(const std::string& name);
void unregisterRule(const SP<IRule>& rule);
void updateAllRules();
void cleanExecRules();
void clearAllRules();
const std::vector<SP<IRule>>& rules();
private:
std::vector<SP<IRule>> m_rules;
};
SP<CRuleEngine> ruleEngine();
}

149
src/desktop/rule/Rule.cpp Normal file
View file

@ -0,0 +1,149 @@
#include "Rule.hpp"
#include "../../debug/Log.hpp"
#include <re2/re2.h>
#include "matchEngine/RegexMatchEngine.hpp"
#include "matchEngine/BoolMatchEngine.hpp"
#include "matchEngine/IntMatchEngine.hpp"
#include "matchEngine/WorkspaceMatchEngine.hpp"
#include "matchEngine/TagMatchEngine.hpp"
using namespace Desktop;
using namespace Desktop::Rule;
static const std::unordered_map<eRuleProperty, std::string> MATCH_PROP_STRINGS = {
{RULE_PROP_CLASS, "class"}, //
{RULE_PROP_TITLE, "title"}, //
{RULE_PROP_INITIAL_CLASS, "initial_class"}, //
{RULE_PROP_INITIAL_TITLE, "initial_title"}, //
{RULE_PROP_FLOATING, "float"}, //
{RULE_PROP_TAG, "tag"}, //
{RULE_PROP_XWAYLAND, "xwayland"}, //
{RULE_PROP_FULLSCREEN, "fullscreen"}, //
{RULE_PROP_PINNED, "pin"}, //
{RULE_PROP_FOCUS, "focus"}, //
{RULE_PROP_GROUP, "group"}, //
{RULE_PROP_MODAL, "modal"}, //
{RULE_PROP_FULLSCREENSTATE_INTERNAL, "fullscreen_state_internal"}, //
{RULE_PROP_FULLSCREENSTATE_CLIENT, "fullscreen_state_client"}, //
{RULE_PROP_ON_WORKSPACE, "workspace"}, //
{RULE_PROP_CONTENT, "content"}, //
{RULE_PROP_XDG_TAG, "xdg_tag"}, //
{RULE_PROP_NAMESPACE, "namespace"}, //
};
static const std::unordered_map<eRuleProperty, eRuleMatchEngine> RULE_ENGINES = {
{RULE_PROP_CLASS, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_TITLE, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_INITIAL_CLASS, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_INITIAL_TITLE, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_FLOATING, RULE_MATCH_ENGINE_BOOL}, //
{RULE_PROP_TAG, RULE_MATCH_ENGINE_TAG}, //
{RULE_PROP_XWAYLAND, RULE_MATCH_ENGINE_BOOL}, //
{RULE_PROP_FULLSCREEN, RULE_MATCH_ENGINE_BOOL}, //
{RULE_PROP_PINNED, RULE_MATCH_ENGINE_BOOL}, //
{RULE_PROP_FOCUS, RULE_MATCH_ENGINE_BOOL}, //
{RULE_PROP_GROUP, RULE_MATCH_ENGINE_BOOL}, //
{RULE_PROP_MODAL, RULE_MATCH_ENGINE_BOOL}, //
{RULE_PROP_FULLSCREENSTATE_INTERNAL, RULE_MATCH_ENGINE_INT}, //
{RULE_PROP_FULLSCREENSTATE_CLIENT, RULE_MATCH_ENGINE_INT}, //
{RULE_PROP_ON_WORKSPACE, RULE_MATCH_ENGINE_WORKSPACE}, //
{RULE_PROP_CONTENT, RULE_MATCH_ENGINE_INT}, //
{RULE_PROP_XDG_TAG, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_NAMESPACE, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_EXEC_TOKEN, RULE_MATCH_ENGINE_REGEX}, //
};
const std::vector<std::string>& Rule::allMatchPropStrings() {
static std::vector<std::string> strings;
static bool once = true;
if (once) {
for (const auto& [k, v] : MATCH_PROP_STRINGS) {
strings.emplace_back(v);
}
once = false;
}
return strings;
}
std::optional<eRuleProperty> Rule::matchPropFromString(const std::string_view& s) {
const auto IT = std::ranges::find_if(MATCH_PROP_STRINGS, [&s](const auto& el) { return el.second == s; });
if (IT == MATCH_PROP_STRINGS.end())
return std::nullopt;
return IT->first;
}
std::optional<eRuleProperty> Rule::matchPropFromString(const std::string& s) {
return matchPropFromString(std::string_view{s});
}
IRule::IRule(const std::string& name) : m_name(name) {
;
}
void IRule::registerMatch(eRuleProperty p, const std::string& s) {
if (!RULE_ENGINES.contains(p)) {
Debug::log(ERR, "BUG THIS: IRule: RULE_ENGINES does not contain rule idx {}", sc<std::underlying_type_t<eRuleProperty>>(p));
return;
}
switch (RULE_ENGINES.at(p)) {
case RULE_MATCH_ENGINE_REGEX: m_matchEngines[p] = makeUnique<CRegexMatchEngine>(s); break;
case RULE_MATCH_ENGINE_BOOL: m_matchEngines[p] = makeUnique<CBoolMatchEngine>(s); break;
case RULE_MATCH_ENGINE_INT: m_matchEngines[p] = makeUnique<CIntMatchEngine>(s); break;
case RULE_MATCH_ENGINE_WORKSPACE: m_matchEngines[p] = makeUnique<CWorkspaceMatchEngine>(s); break;
case RULE_MATCH_ENGINE_TAG: m_matchEngines[p] = makeUnique<CTagMatchEngine>(s); break;
}
m_mask |= p;
}
std::underlying_type_t<eRuleProperty> IRule::getPropertiesMask() {
return m_mask;
}
bool IRule::has(eRuleProperty p) {
return m_matchEngines.contains(p);
}
bool IRule::matches(eRuleProperty p, const std::string& s) {
if (!has(p))
return false;
return m_matchEngines[p]->match(s);
}
bool IRule::matches(eRuleProperty p, bool b) {
if (!has(p))
return false;
return m_matchEngines[p]->match(b);
}
const std::string& IRule::name() {
return m_name;
}
void IRule::markAsExecRule(const std::string& token, bool persistent) {
m_execData.isExecRule = true;
m_execData.isExecPersistent = persistent;
m_execData.token = token;
m_execData.expiresAt = Time::steadyNow() + std::chrono::minutes(1);
}
bool IRule::isExecRule() {
return m_execData.isExecRule;
}
bool IRule::isExecPersistent() {
return m_execData.isExecPersistent;
}
bool IRule::execExpired() {
return Time::steadyNow() > m_execData.expiresAt;
}
const std::string& IRule::execToken() {
return m_execData.token;
}

84
src/desktop/rule/Rule.hpp Normal file
View file

@ -0,0 +1,84 @@
#pragma once
#include "matchEngine/MatchEngine.hpp"
#include "../../helpers/memory/Memory.hpp"
#include "../../helpers/time/Time.hpp"
#include <vector>
#include <unordered_map>
#include <cstdint>
#include <optional>
namespace Desktop::Rule {
enum eRuleProperty : uint32_t {
RULE_PROP_NONE = 0,
RULE_PROP_CLASS = (1 << 0),
RULE_PROP_TITLE = (1 << 1),
RULE_PROP_INITIAL_CLASS = (1 << 2),
RULE_PROP_INITIAL_TITLE = (1 << 3),
RULE_PROP_FLOATING = (1 << 4),
RULE_PROP_TAG = (1 << 5),
RULE_PROP_XWAYLAND = (1 << 6),
RULE_PROP_FULLSCREEN = (1 << 7),
RULE_PROP_PINNED = (1 << 8),
RULE_PROP_FOCUS = (1 << 9),
RULE_PROP_GROUP = (1 << 10),
RULE_PROP_MODAL = (1 << 11),
RULE_PROP_FULLSCREENSTATE_INTERNAL = (1 << 12),
RULE_PROP_FULLSCREENSTATE_CLIENT = (1 << 13),
RULE_PROP_ON_WORKSPACE = (1 << 14),
RULE_PROP_CONTENT = (1 << 15),
RULE_PROP_XDG_TAG = (1 << 16),
RULE_PROP_NAMESPACE = (1 << 17),
RULE_PROP_EXEC_TOKEN = (1 << 18),
RULE_PROP_ALL = std::numeric_limits<std::underlying_type_t<eRuleProperty>>::max(),
};
enum eRuleType : uint8_t {
RULE_TYPE_WINDOW = 0,
RULE_TYPE_LAYER,
};
std::optional<eRuleProperty> matchPropFromString(const std::string& s);
std::optional<eRuleProperty> matchPropFromString(const std::string_view& s);
const std::vector<std::string>& allMatchPropStrings();
class IRule {
public:
virtual ~IRule() = default;
virtual eRuleType type() = 0;
virtual std::underlying_type_t<eRuleProperty> getPropertiesMask();
void registerMatch(eRuleProperty, const std::string&);
void markAsExecRule(const std::string& token, bool persistent = false);
bool isExecRule();
bool isExecPersistent();
bool execExpired();
const std::string& execToken();
const std::string& name();
protected:
IRule(const std::string& name = "");
bool matches(eRuleProperty, const std::string& s);
bool matches(eRuleProperty, bool b);
bool has(eRuleProperty);
//
std::unordered_map<eRuleProperty, UP<IMatchEngine>> m_matchEngines;
private:
std::underlying_type_t<eRuleProperty> m_mask = 0;
std::string m_name = "";
struct {
bool isExecRule = false;
bool isExecPersistent = false;
std::string token;
Time::steady_tp expiresAt;
} m_execData;
};
}

View file

@ -0,0 +1,81 @@
#pragma once
#include <string>
#include <vector>
#include <type_traits>
#include <cstdint>
#include <optional>
namespace Desktop::Rule {
template <typename T>
class IEffectContainer {
static_assert(std::is_enum_v<T>);
protected:
const std::string DEFAULT_MISSING_KEY = "";
public:
// Make sure we're using at least a uint16_t for dynamic registrations to not overflow.
// 32k should be enough
using storageType = std::conditional_t<(sizeof(std::underlying_type_t<T>) >= 2), std::underlying_type_t<T>, uint16_t>;
IEffectContainer(std::vector<std::string>&& defaultKeys) : m_keys(std::move(defaultKeys)), m_originalSize(m_keys.size()) {
;
}
virtual ~IEffectContainer() = default;
virtual storageType registerEffect(std::string&& name) {
if (m_keys.size() >= std::numeric_limits<storageType>::max())
return 0;
if (auto it = std::ranges::find(m_keys, name); it != m_keys.end())
return it - m_keys.begin();
m_keys.emplace_back(std::move(name));
return m_keys.size() - 1;
}
virtual void unregisterEffect(storageType id) {
if (id >= m_keys.size())
return;
m_keys[id] = DEFAULT_MISSING_KEY;
}
virtual void unregisterEffect(const std::string& name) {
for (auto& key : m_keys) {
if (key == name) {
key = DEFAULT_MISSING_KEY;
break;
}
}
}
virtual const std::string& get(storageType idx) {
if (idx >= m_keys.size())
return DEFAULT_MISSING_KEY;
return m_keys[idx];
}
virtual std::optional<storageType> get(const std::string_view& s) {
for (storageType i = 0; i < m_keys.size(); ++i) {
if (m_keys[i] == s)
return i;
}
return std::nullopt;
}
virtual const std::vector<std::string>& allEffectStrings() {
return m_keys;
}
// whether the effect has been added dynamically as opposed to in the ctor.
virtual bool isEffectDynamic(storageType i) {
return i >= m_originalSize;
}
protected:
std::vector<std::string> m_keys;
size_t m_originalSize = 0;
};
};

View file

@ -0,0 +1,43 @@
#include "LayerRule.hpp"
#include "../../../debug/Log.hpp"
#include "../../LayerSurface.hpp"
using namespace Desktop;
using namespace Desktop::Rule;
CLayerRule::CLayerRule(const std::string& name) : IRule(name) {
;
}
eRuleType CLayerRule::type() {
return RULE_TYPE_LAYER;
}
void CLayerRule::addEffect(CLayerRule::storageType e, const std::string& result) {
m_effects.emplace_back(std::make_pair<>(e, result));
}
const std::vector<std::pair<CLayerRule::storageType, std::string>>& CLayerRule::effects() {
return m_effects;
}
bool CLayerRule::matches(PHLLS ls) {
if (m_matchEngines.empty())
return false;
for (const auto& [prop, engine] : m_matchEngines) {
switch (prop) {
default: {
Debug::log(TRACE, "CLayerRule::matches: skipping prop entry {}", sc<std::underlying_type_t<eRuleProperty>>(prop));
break;
}
case RULE_PROP_NAMESPACE:
if (!engine->match(ls->m_namespace))
return false;
break;
}
}
return true;
}

View file

@ -0,0 +1,25 @@
#pragma once
#include "../Rule.hpp"
#include "../../DesktopTypes.hpp"
#include "LayerRuleEffectContainer.hpp"
namespace Desktop::Rule {
class CLayerRule : public IRule {
public:
using storageType = CLayerRuleEffectContainer::storageType;
CLayerRule(const std::string& name = "");
virtual ~CLayerRule() = default;
virtual eRuleType type();
void addEffect(storageType e, const std::string& result);
const std::vector<std::pair<storageType, std::string>>& effects();
bool matches(PHLLS w);
private:
std::vector<std::pair<storageType, std::string>> m_effects;
};
};

View file

@ -0,0 +1,128 @@
#include "LayerRuleApplicator.hpp"
#include "LayerRule.hpp"
#include "../Engine.hpp"
#include "../../LayerSurface.hpp"
#include "../../types/OverridableVar.hpp"
#include "../../../helpers/MiscFunctions.hpp"
using namespace Desktop;
using namespace Desktop::Rule;
CLayerRuleApplicator::CLayerRuleApplicator(PHLLS ls) : m_ls(ls) {
;
}
void CLayerRuleApplicator::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(noanim)
UNSET(blur)
UNSET(blurPopups)
UNSET(dimAround)
UNSET(xray)
UNSET(noScreenShare)
UNSET(order)
UNSET(aboveLock)
UNSET(ignoreAlpha)
UNSET(animationStyle)
}
void CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) {
for (const auto& [key, effect] : rule->effects()) {
switch (key) {
case LAYER_RULE_EFFECT_NONE: {
Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??");
break;
}
case LAYER_RULE_EFFECT_NO_ANIM: {
m_noanim.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
m_noanim.second |= rule->getPropertiesMask();
break;
}
case LAYER_RULE_EFFECT_BLUR: {
m_blur.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
m_blur.second |= rule->getPropertiesMask();
break;
}
case LAYER_RULE_EFFECT_BLUR_POPUPS: {
m_blurPopups.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
m_blurPopups.second |= rule->getPropertiesMask();
break;
}
case LAYER_RULE_EFFECT_DIM_AROUND: {
m_dimAround.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
m_dimAround.second |= rule->getPropertiesMask();
break;
}
case LAYER_RULE_EFFECT_XRAY: {
m_xray.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
m_xray.second |= rule->getPropertiesMask();
break;
}
case LAYER_RULE_EFFECT_NO_SCREEN_SHARE: {
m_noScreenShare.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE);
m_noScreenShare.second |= rule->getPropertiesMask();
break;
}
case LAYER_RULE_EFFECT_ORDER: {
try {
m_noScreenShare.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);
m_noScreenShare.second |= rule->getPropertiesMask();
} catch (...) { Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); }
break;
}
case LAYER_RULE_EFFECT_ABOVE_LOCK: {
try {
m_aboveLock.first.set(std::clamp(std::stoull(effect), 0ULL, 2ULL), Types::PRIORITY_WINDOW_RULE);
m_aboveLock.second |= rule->getPropertiesMask();
} catch (...) { Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); }
break;
}
case LAYER_RULE_EFFECT_IGNORE_ALPHA: {
try {
m_ignoreAlpha.first.set(std::clamp(std::stof(effect), 0.F, 1.F), Types::PRIORITY_WINDOW_RULE);
m_ignoreAlpha.second |= rule->getPropertiesMask();
} catch (...) { Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); }
break;
}
case LAYER_RULE_EFFECT_ANIMATION: {
m_animationStyle.first.set(effect, Types::PRIORITY_WINDOW_RULE);
m_animationStyle.second |= rule->getPropertiesMask();
break;
}
}
}
}
void CLayerRuleApplicator::propertiesChanged(std::underlying_type_t<eRuleProperty> props) {
if (!m_ls)
return;
resetProps(props);
// FIXME: this will not update properties correctly if we implement dynamic rules for
// layers, due to effects overlapping on 0 prop intersection.
// See WindowRule.cpp, and ::propertiesChanged there.
for (const auto& r : ruleEngine()->rules()) {
if (r->type() != RULE_TYPE_LAYER)
continue;
if (!(r->getPropertiesMask() & props))
continue;
auto wr = reinterpretPointerCast<CLayerRule>(r);
if (!wr->matches(m_ls.lock()))
continue;
applyDynamicRule(wr);
}
}

View file

@ -0,0 +1,60 @@
#pragma once
#include "../../DesktopTypes.hpp"
#include "../Rule.hpp"
#include "../../types/OverridableVar.hpp"
#include "../../../helpers/math/Math.hpp"
#include "../../../config/ConfigDataValues.hpp"
namespace Desktop::Rule {
class CLayerRule;
class CLayerRuleApplicator {
public:
CLayerRuleApplicator(PHLLS ls);
~CLayerRuleApplicator() = default;
CLayerRuleApplicator(const CLayerRuleApplicator&) = delete;
CLayerRuleApplicator(CLayerRuleApplicator&) = delete;
CLayerRuleApplicator(CLayerRuleApplicator&&) = delete;
void propertiesChanged(std::underlying_type_t<eRuleProperty> props);
void resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE);
#define COMMA ,
#define DEFINE_PROP(type, name, def) \
private: \
std::pair<Types::COverridableVar<type>, std::underlying_type_t<eRuleProperty>> m_##name = {def, RULE_PROP_NONE}; \
\
public: \
Types::COverridableVar<type>& name() { \
return m_##name.first; \
} \
void name##Override(const Types::COverridableVar<type>& other) { \
m_##name.first = other; \
}
// dynamic props
DEFINE_PROP(bool, noanim, false)
DEFINE_PROP(bool, blur, false)
DEFINE_PROP(bool, blurPopups, false)
DEFINE_PROP(bool, dimAround, false)
DEFINE_PROP(bool, xray, false)
DEFINE_PROP(bool, noScreenShare, false)
DEFINE_PROP(Hyprlang::INT, order, 0)
DEFINE_PROP(Hyprlang::INT, aboveLock, 0)
DEFINE_PROP(Hyprlang::FLOAT, ignoreAlpha, 0.F)
DEFINE_PROP(std::string, animationStyle, std::string(""))
#undef COMMA
#undef DEFINE_PROP
private:
PHLLSREF m_ls;
void applyDynamicRule(const SP<CLayerRule>& rule);
};
};

View file

@ -0,0 +1,33 @@
#include "LayerRuleEffectContainer.hpp"
using namespace Desktop;
using namespace Desktop::Rule;
//
SP<CLayerRuleEffectContainer> Rule::layerEffects() {
static SP<CLayerRuleEffectContainer> container = makeShared<CLayerRuleEffectContainer>();
return container;
}
static const std::vector<std::string> EFFECT_STRINGS = {
"__internal_none", //
"no_anim", //
"blur", //
"blur_popups", //
"ignore_alpha", //
"dim_around", //
"xray", //
"animation", //
"order", //
"above_lock", //
"no_screen_share", //
"__internal_last_static", //
};
// This is here so that if we change the rules, we get reminded to update
// the strings.
static_assert(LAYER_RULE_EFFECT_LAST_STATIC == 11);
CLayerRuleEffectContainer::CLayerRuleEffectContainer() : IEffectContainer<eLayerRuleEffect>(std::vector<std::string>{EFFECT_STRINGS}) {
;
}

View file

@ -0,0 +1,33 @@
#pragma once
#include "../effect/EffectContainer.hpp"
#include "../../../helpers/memory/Memory.hpp"
#pragma once
namespace Desktop::Rule {
enum eLayerRuleEffect : uint8_t {
LAYER_RULE_EFFECT_NONE = 0,
LAYER_RULE_EFFECT_NO_ANIM,
LAYER_RULE_EFFECT_BLUR,
LAYER_RULE_EFFECT_BLUR_POPUPS,
LAYER_RULE_EFFECT_IGNORE_ALPHA,
LAYER_RULE_EFFECT_DIM_AROUND,
LAYER_RULE_EFFECT_XRAY,
LAYER_RULE_EFFECT_ANIMATION,
LAYER_RULE_EFFECT_ORDER,
LAYER_RULE_EFFECT_ABOVE_LOCK,
LAYER_RULE_EFFECT_NO_SCREEN_SHARE,
LAYER_RULE_EFFECT_LAST_STATIC,
};
class CLayerRuleEffectContainer : public IEffectContainer<eLayerRuleEffect> {
public:
CLayerRuleEffectContainer();
virtual ~CLayerRuleEffectContainer() = default;
};
SP<CLayerRuleEffectContainer> layerEffects();
};

View file

@ -0,0 +1,12 @@
#include "BoolMatchEngine.hpp"
#include "../../../helpers/MiscFunctions.hpp"
using namespace Desktop::Rule;
CBoolMatchEngine::CBoolMatchEngine(const std::string& s) : m_value(truthy(s)) {
;
}
bool CBoolMatchEngine::match(bool other) {
return other == m_value;
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "MatchEngine.hpp"
namespace Desktop::Rule {
class CBoolMatchEngine : public IMatchEngine {
public:
CBoolMatchEngine(const std::string&);
virtual ~CBoolMatchEngine() = default;
virtual bool match(bool other);
private:
bool m_value = false;
};
}

View file

@ -0,0 +1,14 @@
#include "IntMatchEngine.hpp"
#include "../../../debug/Log.hpp"
using namespace Desktop::Rule;
CIntMatchEngine::CIntMatchEngine(const std::string& s) {
try {
m_value = std::stoi(s);
} catch (...) { Debug::log(ERR, "CIntMatchEngine: invalid input {}", s); }
}
bool CIntMatchEngine::match(int other) {
return m_value == other;
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "MatchEngine.hpp"
namespace Desktop::Rule {
class CIntMatchEngine : public IMatchEngine {
public:
CIntMatchEngine(const std::string&);
virtual ~CIntMatchEngine() = default;
virtual bool match(int other);
private:
int m_value = 0;
};
}

View file

@ -0,0 +1,23 @@
#include "MatchEngine.hpp"
using namespace Desktop::Rule;
bool IMatchEngine::match(const std::string&) {
return false;
}
bool IMatchEngine::match(bool) {
return false;
}
bool IMatchEngine::match(int) {
return false;
}
bool IMatchEngine::match(PHLWORKSPACE) {
return false;
}
bool IMatchEngine::match(const CTagKeeper& keeper) {
return false;
}

View file

@ -0,0 +1,28 @@
#pragma once
#include "../../DesktopTypes.hpp"
class CTagKeeper;
namespace Desktop::Rule {
enum eRuleMatchEngine : uint8_t {
RULE_MATCH_ENGINE_REGEX = 0,
RULE_MATCH_ENGINE_BOOL,
RULE_MATCH_ENGINE_INT,
RULE_MATCH_ENGINE_WORKSPACE,
RULE_MATCH_ENGINE_TAG,
};
class IMatchEngine {
public:
virtual ~IMatchEngine() = default;
virtual bool match(const std::string&);
virtual bool match(bool);
virtual bool match(int);
virtual bool match(PHLWORKSPACE);
virtual bool match(const CTagKeeper& keeper);
protected:
IMatchEngine() = default;
};
};

View file

@ -0,0 +1,17 @@
#include "RegexMatchEngine.hpp"
#include <re2/re2.h>
using namespace Desktop::Rule;
CRegexMatchEngine::CRegexMatchEngine(const std::string& regex) {
if (regex.starts_with("negative:")) {
m_negative = true;
m_regex = makeUnique<re2::RE2>(regex.substr(9));
return;
}
m_regex = makeUnique<re2::RE2>(regex);
}
bool CRegexMatchEngine::match(const std::string& other) {
return re2::RE2::FullMatch(other, *m_regex) != m_negative;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "MatchEngine.hpp"
#include "../../../helpers/memory/Memory.hpp"
//NOLINTNEXTLINE
namespace re2 {
class RE2;
};
namespace Desktop::Rule {
class CRegexMatchEngine : public IMatchEngine {
public:
CRegexMatchEngine(const std::string& regex);
virtual ~CRegexMatchEngine() = default;
virtual bool match(const std::string& other);
private:
UP<re2::RE2> m_regex;
bool m_negative = false;
};
}

View file

@ -0,0 +1,12 @@
#include "TagMatchEngine.hpp"
#include "../../../helpers/TagKeeper.hpp"
using namespace Desktop::Rule;
CTagMatchEngine::CTagMatchEngine(const std::string& tag) : m_tag(tag) {
;
}
bool CTagMatchEngine::match(const CTagKeeper& keeper) {
return keeper.isTagged(m_tag);
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "MatchEngine.hpp"
namespace Desktop::Rule {
class CTagMatchEngine : public IMatchEngine {
public:
CTagMatchEngine(const std::string& tag);
virtual ~CTagMatchEngine() = default;
virtual bool match(const CTagKeeper& keeper);
private:
std::string m_tag;
};
}

View file

@ -0,0 +1,12 @@
#include "WorkspaceMatchEngine.hpp"
#include "../../Workspace.hpp"
using namespace Desktop::Rule;
CWorkspaceMatchEngine::CWorkspaceMatchEngine(const std::string& s) : m_value(s) {
;
}
bool CWorkspaceMatchEngine::match(PHLWORKSPACE ws) {
return ws->matchesStaticSelector(m_value);
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "MatchEngine.hpp"
namespace Desktop::Rule {
class CWorkspaceMatchEngine : public IMatchEngine {
public:
CWorkspaceMatchEngine(const std::string&);
virtual ~CWorkspaceMatchEngine() = default;
virtual bool match(PHLWORKSPACE ws);
private:
std::string m_value = "";
};
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <unordered_set>
namespace Desktop::Rule {
template <class T>
bool setsIntersect(const std::unordered_set<T>& A, const std::unordered_set<T>& B) {
if (A.size() > B.size())
return setsIntersect(B, A);
for (const auto& e : A) {
if (B.contains(e))
return true;
}
return false;
}
};

View file

@ -0,0 +1,186 @@
#include "WindowRule.hpp"
#include "../../Window.hpp"
#include "../../../helpers/Monitor.hpp"
#include "../../../Compositor.hpp"
#include "../../../managers/TokenManager.hpp"
using namespace Desktop;
using namespace Desktop::Rule;
std::optional<Vector2D> Rule::parseRelativeVector(PHLWINDOW w, const std::string& s) {
try {
const auto VALUE = s.substr(s.find(' ') + 1);
const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' '));
const auto SIZEYSTR = VALUE.substr(VALUE.find(' ') + 1);
const auto MAXSIZE = w->requestedMaxSize();
const float SIZEX = SIZEXSTR == "max" ? std::clamp(MAXSIZE.x, MIN_WINDOW_SIZE, g_pCompositor->m_lastMonitor->m_size.x) :
stringToPercentage(SIZEXSTR, g_pCompositor->m_lastMonitor->m_size.x);
const float SIZEY = SIZEYSTR == "max" ? std::clamp(MAXSIZE.y, MIN_WINDOW_SIZE, g_pCompositor->m_lastMonitor->m_size.y) :
stringToPercentage(SIZEYSTR, g_pCompositor->m_lastMonitor->m_size.y);
return Vector2D{SIZEX, SIZEY};
} catch (...) { Debug::log(LOG, "Rule size failed, rule: {}", s); }
return std::nullopt;
}
CWindowRule::CWindowRule(const std::string& name) : IRule(name) {
;
}
eRuleType CWindowRule::type() {
return RULE_TYPE_WINDOW;
}
void CWindowRule::addEffect(CWindowRule::storageType e, const std::string& result) {
m_effects.emplace_back(std::make_pair<>(e, result));
m_effectSet.emplace(e);
}
const std::vector<std::pair<CWindowRule::storageType, std::string>>& CWindowRule::effects() {
return m_effects;
}
bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
if (m_matchEngines.empty())
return false;
for (const auto& [prop, engine] : m_matchEngines) {
switch (prop) {
default: {
Debug::log(TRACE, "CWindowRule::matches: skipping prop entry {}", sc<std::underlying_type_t<eRuleProperty>>(prop));
break;
}
case RULE_PROP_TITLE:
if (!engine->match(w->m_title))
return false;
break;
case RULE_PROP_INITIAL_TITLE:
if (!engine->match(w->m_initialTitle))
return false;
break;
case RULE_PROP_CLASS:
if (!engine->match(w->m_class))
return false;
break;
case RULE_PROP_INITIAL_CLASS:
if (!engine->match(w->m_initialClass))
return false;
break;
case RULE_PROP_FLOATING:
if (!engine->match(w->m_isFloating))
return false;
break;
case RULE_PROP_TAG:
if (!engine->match(w->m_ruleApplicator->m_tagKeeper))
return false;
break;
case RULE_PROP_XWAYLAND:
if (!engine->match(w->m_isX11))
return false;
break;
case RULE_PROP_FULLSCREEN:
if (!engine->match(w->m_fullscreenState.internal != 0))
return false;
break;
case RULE_PROP_PINNED:
if (!engine->match(w->m_pinned))
return false;
break;
case RULE_PROP_FOCUS:
if (!engine->match(g_pCompositor->m_lastWindow == w))
return false;
break;
case RULE_PROP_GROUP:
if (!engine->match(w->m_groupData.pNextWindow))
return false;
break;
case RULE_PROP_MODAL:
if (!engine->match(w->isModal()))
return false;
break;
case RULE_PROP_FULLSCREENSTATE_INTERNAL:
if (!engine->match(w->m_fullscreenState.internal))
return false;
break;
case RULE_PROP_FULLSCREENSTATE_CLIENT:
if (!engine->match(w->m_fullscreenState.client))
return false;
break;
case RULE_PROP_ON_WORKSPACE:
if (!engine->match(w->m_workspace))
return false;
break;
case RULE_PROP_CONTENT:
if (!engine->match(NContentType::toString(w->getContentType())))
return false;
break;
case RULE_PROP_XDG_TAG:
if (w->xdgTag().has_value() && !engine->match(*w->xdgTag()))
return false;
break;
case RULE_PROP_EXEC_TOKEN:
// this is only allowed on static rules, we don't need it on dynamic plus it's expensive
if (!allowEnvLookup)
break;
const auto ENV = w->getEnv();
if (ENV.contains(EXEC_RULE_ENV_NAME)) {
const auto TKN = ENV.at(EXEC_RULE_ENV_NAME);
if (!engine->match(TKN))
return false;
break;
}
return false;
}
}
return true;
}
SP<CWindowRule> CWindowRule::buildFromExecString(std::string&& s) {
CVarList2 varlist(std::move(s), 0, ';');
SP<CWindowRule> wr = makeShared<CWindowRule>("__exec_rule");
for (const auto& el : varlist) {
// split element by space, can't do better
size_t spacePos = el.find(' ');
if (spacePos != std::string::npos) {
// great, split and try to parse
auto LHS = el.substr(0, spacePos);
const auto EFFECT = windowEffects()->get(LHS);
if (!EFFECT.has_value() || *EFFECT == WINDOW_RULE_EFFECT_NONE)
continue; // invalid...
wr->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)});
continue;
}
// assume 1 maybe...
const auto EFFECT = windowEffects()->get(el);
if (!EFFECT.has_value() || *EFFECT == WINDOW_RULE_EFFECT_NONE)
continue; // invalid...
wr->addEffect(*EFFECT, std::string{"1"});
}
const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1));
wr->markAsExecRule(TOKEN, false /* TODO: could be nice. */);
wr->registerMatch(RULE_PROP_EXEC_TOKEN, TOKEN);
return wr;
}
const std::unordered_set<CWindowRule::storageType>& CWindowRule::effectsSet() {
return m_effectSet;
}

View file

@ -0,0 +1,37 @@
#pragma once
#include "../Rule.hpp"
#include "../../DesktopTypes.hpp"
#include "WindowRuleEffectContainer.hpp"
#include "../../../helpers/math/Math.hpp"
#include <unordered_set>
namespace Desktop::Rule {
constexpr const char* EXEC_RULE_ENV_NAME = "HL_EXEC_RULE_TOKEN";
std::optional<Vector2D> parseRelativeVector(PHLWINDOW w, const std::string& s);
class CWindowRule : public IRule {
private:
using storageType = CWindowRuleEffectContainer::storageType;
public:
CWindowRule(const std::string& name = "");
virtual ~CWindowRule() = default;
static SP<CWindowRule> buildFromExecString(std::string&&);
virtual eRuleType type();
void addEffect(storageType e, const std::string& result);
const std::vector<std::pair<storageType, std::string>>& effects();
const std::unordered_set<storageType>& effectsSet();
bool matches(PHLWINDOW w, bool allowEnvLookup = false);
private:
std::vector<std::pair<storageType, std::string>> m_effects;
std::unordered_set<storageType> m_effectSet;
};
};

View file

@ -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());
}

View file

@ -0,0 +1,148 @@
#pragma once
#include <unordered_map>
#include "WindowRuleEffectContainer.hpp"
#include "../../DesktopTypes.hpp"
#include "../Rule.hpp"
#include "../../types/OverridableVar.hpp"
#include "../../../helpers/math/Math.hpp"
#include "../../../helpers/TagKeeper.hpp"
#include "../../../config/ConfigDataValues.hpp"
namespace Desktop::Rule {
class CWindowRule;
enum eIdleInhibitMode : uint8_t {
IDLEINHIBIT_NONE = 0,
IDLEINHIBIT_ALWAYS,
IDLEINHIBIT_FULLSCREEN,
IDLEINHIBIT_FOCUS
};
class CWindowRuleApplicator {
public:
CWindowRuleApplicator(PHLWINDOW w);
~CWindowRuleApplicator() = default;
CWindowRuleApplicator(const CWindowRuleApplicator&) = delete;
CWindowRuleApplicator(CWindowRuleApplicator&) = delete;
CWindowRuleApplicator(CWindowRuleApplicator&&) = delete;
void propertiesChanged(std::underlying_type_t<eRuleProperty> props);
void resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE);
void readStaticRules();
void applyStaticRules();
// static props
struct {
std::string monitor, workspace, group;
std::optional<bool> floating;
bool fullscreen = false;
bool maximize = false;
bool pseudo = false;
bool pin = false;
bool noInitialFocus = false;
std::optional<int> fullscreenStateClient;
std::optional<int> fullscreenStateInternal;
std::optional<int> center;
std::optional<int> content;
std::optional<int> noCloseFor;
std::string size, position;
std::vector<std::string> suppressEvent;
} static_;
struct SCustomPropContainer {
CWindowRuleEffectContainer::storageType idx = WINDOW_RULE_EFFECT_NONE;
std::underlying_type_t<eRuleProperty> propMask = RULE_PROP_NONE;
std::string effect;
};
// This struct holds props that were dynamically registered. Plugins may read this.
struct {
std::unordered_map<CWindowRuleEffectContainer::storageType, UP<SCustomPropContainer>> props;
} m_otherProps;
#define COMMA ,
#define DEFINE_PROP(type, name, def) \
private: \
std::pair<Types::COverridableVar<type>, std::underlying_type_t<eRuleProperty>> m_##name = {def, RULE_PROP_NONE}; \
\
public: \
Types::COverridableVar<type>& name() { \
return m_##name.first; \
} \
void name##Override(const Types::COverridableVar<type>& other) { \
m_##name.first = other; \
}
// dynamic props
DEFINE_PROP(Types::SAlphaValue, alpha, Types::SAlphaValue{})
DEFINE_PROP(Types::SAlphaValue, alphaInactive, Types::SAlphaValue{})
DEFINE_PROP(Types::SAlphaValue, alphaFullscreen, Types::SAlphaValue{})
DEFINE_PROP(bool, allowsInput, false)
DEFINE_PROP(bool, decorate, true)
DEFINE_PROP(bool, focusOnActivate, false)
DEFINE_PROP(bool, keepAspectRatio, false)
DEFINE_PROP(bool, nearestNeighbor, false)
DEFINE_PROP(bool, noAnim, false)
DEFINE_PROP(bool, noBlur, false)
DEFINE_PROP(bool, noDim, false)
DEFINE_PROP(bool, noFocus, false)
DEFINE_PROP(bool, noMaxSize, false)
DEFINE_PROP(bool, noShadow, false)
DEFINE_PROP(bool, noShortcutsInhibit, false)
DEFINE_PROP(bool, opaque, false)
DEFINE_PROP(bool, dimAround, false)
DEFINE_PROP(bool, RGBX, false)
DEFINE_PROP(bool, syncFullscreen, true)
DEFINE_PROP(bool, tearing, false)
DEFINE_PROP(bool, xray, false)
DEFINE_PROP(bool, renderUnfocused, false)
DEFINE_PROP(bool, noFollowMouse, false)
DEFINE_PROP(bool, noScreenShare, false)
DEFINE_PROP(bool, noVRR, false)
DEFINE_PROP(bool, persistentSize, false)
DEFINE_PROP(bool, stayFocused, false)
DEFINE_PROP(int, idleInhibitMode, false)
DEFINE_PROP(Hyprlang::INT, borderSize, {std::string("general:border_size") COMMA sc<Hyprlang::INT>(0) COMMA std::nullopt})
DEFINE_PROP(Hyprlang::INT, rounding, {std::string("decoration:rounding") COMMA sc<Hyprlang::INT>(0) COMMA std::nullopt})
DEFINE_PROP(Hyprlang::FLOAT, roundingPower, {std::string("decoration:rounding_power")})
DEFINE_PROP(Hyprlang::FLOAT, scrollMouse, {std::string("input:scroll_factor")})
DEFINE_PROP(Hyprlang::FLOAT, scrollTouchpad, {std::string("input:touchpad:scroll_factor")})
DEFINE_PROP(std::string, animationStyle, std::string(""))
DEFINE_PROP(Vector2D, maxSize, Vector2D{})
DEFINE_PROP(Vector2D, minSize, Vector2D{})
DEFINE_PROP(CGradientValueData, activeBorderColor, {})
DEFINE_PROP(CGradientValueData, inactiveBorderColor, {})
std::vector<std::pair<std::string, std::underlying_type_t<eRuleProperty>>> m_dynamicTags;
CTagKeeper m_tagKeeper;
#undef COMMA
#undef DEFINE_PROP
private:
PHLWINDOWREF m_window;
struct SRuleResult {
bool needsRelayout = false;
bool tagsChanged = false;
};
SRuleResult applyDynamicRule(const SP<CWindowRule>& rule);
SRuleResult applyStaticRule(const SP<CWindowRule>& rule);
};
};

View file

@ -0,0 +1,76 @@
#include "WindowRuleEffectContainer.hpp"
using namespace Desktop;
using namespace Desktop::Rule;
//
SP<CWindowRuleEffectContainer> Rule::windowEffects() {
static SP<CWindowRuleEffectContainer> container = makeShared<CWindowRuleEffectContainer>();
return container;
}
static const std::vector<std::string> EFFECT_STRINGS = {
"__internal_none", //
"float", //
"tile", //
"fullscreen", //
"maximize", //
"fullscreen_state", //
"move", //
"size", //
"center", //
"pseudo", //
"monitor", //
"workspace", //
"no_initial_focus", //
"pin", //
"group", //
"suppress_event", //
"content", //
"no_close_for", //
"rounding", //
"rounding_power", //
"persistent_size", //
"animation", //
"border_color", //
"idle_inhibit", //
"opacity", //
"tag", //
"max_size", //
"min_size", //
"border_size", //
"allows_input", //
"dim_around", //
"decorate", //
"focus_on_activate", //
"keep_aspect_ratio", //
"nearest_neighbor", //
"no_anim", //
"no_blur", //
"no_dim", //
"no_focus", //
"no_follow_mouse", //
"no_max_size", //
"no_shadow", //
"no_shortcuts_inhibit", //
"opaque", //
"force_rgbx", //
"sync_fullscreen", //
"immediate", //
"xray", //
"render_unfocused", //
"no_screen_share", //
"no_vrr", //
"scroll_mouse", //
"scroll_touchpad", //
"stay_focused", //
"__internal_last_static", //
};
// This is here so that if we change the rules, we get reminded to update
// the strings.
static_assert(WINDOW_RULE_EFFECT_LAST_STATIC == 54);
CWindowRuleEffectContainer::CWindowRuleEffectContainer() : IEffectContainer<eWindowRuleEffect>(std::vector<std::string>{EFFECT_STRINGS}) {
;
}

View file

@ -0,0 +1,79 @@
#pragma once
#include "../effect/EffectContainer.hpp"
#include "../../../helpers/memory/Memory.hpp"
#pragma once
namespace Desktop::Rule {
enum eWindowRuleEffect : uint8_t {
WINDOW_RULE_EFFECT_NONE = 0,
// static
WINDOW_RULE_EFFECT_FLOAT,
WINDOW_RULE_EFFECT_TILE,
WINDOW_RULE_EFFECT_FULLSCREEN,
WINDOW_RULE_EFFECT_MAXIMIZE,
WINDOW_RULE_EFFECT_FULLSCREENSTATE,
WINDOW_RULE_EFFECT_MOVE,
WINDOW_RULE_EFFECT_SIZE,
WINDOW_RULE_EFFECT_CENTER,
WINDOW_RULE_EFFECT_PSEUDO,
WINDOW_RULE_EFFECT_MONITOR,
WINDOW_RULE_EFFECT_WORKSPACE,
WINDOW_RULE_EFFECT_NOINITIALFOCUS,
WINDOW_RULE_EFFECT_PIN,
WINDOW_RULE_EFFECT_GROUP,
WINDOW_RULE_EFFECT_SUPPRESSEVENT,
WINDOW_RULE_EFFECT_CONTENT,
WINDOW_RULE_EFFECT_NOCLOSEFOR,
// dynamic
WINDOW_RULE_EFFECT_ROUNDING,
WINDOW_RULE_EFFECT_ROUNDING_POWER,
WINDOW_RULE_EFFECT_PERSISTENT_SIZE,
WINDOW_RULE_EFFECT_ANIMATION,
WINDOW_RULE_EFFECT_BORDER_COLOR,
WINDOW_RULE_EFFECT_IDLE_INHIBIT,
WINDOW_RULE_EFFECT_OPACITY,
WINDOW_RULE_EFFECT_TAG,
WINDOW_RULE_EFFECT_MAX_SIZE,
WINDOW_RULE_EFFECT_MIN_SIZE,
WINDOW_RULE_EFFECT_BORDER_SIZE,
WINDOW_RULE_EFFECT_ALLOWS_INPUT,
WINDOW_RULE_EFFECT_DIM_AROUND,
WINDOW_RULE_EFFECT_DECORATE,
WINDOW_RULE_EFFECT_FOCUS_ON_ACTIVATE,
WINDOW_RULE_EFFECT_KEEP_ASPECT_RATIO,
WINDOW_RULE_EFFECT_NEAREST_NEIGHBOR,
WINDOW_RULE_EFFECT_NO_ANIM,
WINDOW_RULE_EFFECT_NO_BLUR,
WINDOW_RULE_EFFECT_NO_DIM,
WINDOW_RULE_EFFECT_NO_FOCUS,
WINDOW_RULE_EFFECT_NO_FOLLOW_MOUSE,
WINDOW_RULE_EFFECT_NO_MAX_SIZE,
WINDOW_RULE_EFFECT_NO_SHADOW,
WINDOW_RULE_EFFECT_NO_SHORTCUTS_INHIBIT,
WINDOW_RULE_EFFECT_OPAQUE,
WINDOW_RULE_EFFECT_FORCE_RGBX,
WINDOW_RULE_EFFECT_SYNC_FULLSCREEN,
WINDOW_RULE_EFFECT_IMMEDIATE,
WINDOW_RULE_EFFECT_XRAY,
WINDOW_RULE_EFFECT_RENDER_UNFOCUSED,
WINDOW_RULE_EFFECT_NO_SCREEN_SHARE,
WINDOW_RULE_EFFECT_NO_VRR,
WINDOW_RULE_EFFECT_SCROLL_MOUSE,
WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD,
WINDOW_RULE_EFFECT_STAY_FOCUSED,
WINDOW_RULE_EFFECT_LAST_STATIC,
};
class CWindowRuleEffectContainer : public IEffectContainer<eWindowRuleEffect> {
public:
CWindowRuleEffectContainer();
virtual ~CWindowRuleEffectContainer() = default;
};
SP<CWindowRuleEffectContainer> windowEffects();
};