From c2670e9ab90bd657e87a1da2c5322e9007dce01f Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:34:02 +0000 Subject: [PATCH] windowrules: rewrite completely (#12269) Reworks the window rule syntax completely --------- Co-authored-by: Mihai Fufezan --- .github/actions/setup_base/action.yml | 1 + CMakeLists.txt | 3 +- example/hyprland.conf | 47 +- hyprtester/plugin/src/main.cpp | 28 + .../src/tests/clients/pointer-scroll.cpp | 4 +- hyprtester/src/tests/clients/pointer-warp.cpp | 2 +- hyprtester/src/tests/main/hyprctl.cpp | 142 ++-- hyprtester/src/tests/main/tags.cpp | 21 +- hyprtester/src/tests/main/window.cpp | 139 +++- hyprtester/src/tests/main/workspaces.cpp | 2 +- hyprtester/test.conf | 78 +- nix/default.nix | 2 + src/Compositor.cpp | 144 +--- src/Compositor.hpp | 1 - src/config/ConfigDescriptions.hpp | 6 - src/config/ConfigManager.cpp | 740 +++++------------- src/config/ConfigManager.hpp | 29 +- src/debug/HyprCtl.cpp | 134 +++- src/debug/HyprCtl.hpp | 7 +- src/desktop/LayerRule.cpp | 42 - src/desktop/LayerRule.hpp | 33 - src/desktop/LayerSurface.cpp | 85 +- src/desktop/LayerSurface.hpp | 54 +- src/desktop/Rule.cpp | 22 - src/desktop/Rule.hpp | 21 - src/desktop/Window.cpp | 418 ++++------ src/desktop/Window.hpp | 129 +-- src/desktop/WindowOverridableVar.hpp | 132 ---- src/desktop/WindowRule.cpp | 99 --- src/desktop/WindowRule.hpp | 76 -- src/desktop/Workspace.cpp | 2 +- src/desktop/rule/Engine.cpp | 56 ++ src/desktop/rule/Engine.hpp | 24 + src/desktop/rule/Rule.cpp | 149 ++++ src/desktop/rule/Rule.hpp | 84 ++ src/desktop/rule/effect/EffectContainer.hpp | 81 ++ src/desktop/rule/layerRule/LayerRule.cpp | 43 + src/desktop/rule/layerRule/LayerRule.hpp | 25 + .../rule/layerRule/LayerRuleApplicator.cpp | 128 +++ .../rule/layerRule/LayerRuleApplicator.hpp | 60 ++ .../layerRule/LayerRuleEffectContainer.cpp | 33 + .../layerRule/LayerRuleEffectContainer.hpp | 33 + .../rule/matchEngine/BoolMatchEngine.cpp | 12 + .../rule/matchEngine/BoolMatchEngine.hpp | 16 + .../rule/matchEngine/IntMatchEngine.cpp | 14 + .../rule/matchEngine/IntMatchEngine.hpp | 16 + src/desktop/rule/matchEngine/MatchEngine.cpp | 23 + src/desktop/rule/matchEngine/MatchEngine.hpp | 28 + .../rule/matchEngine/RegexMatchEngine.cpp | 17 + .../rule/matchEngine/RegexMatchEngine.hpp | 23 + .../rule/matchEngine/TagMatchEngine.cpp | 12 + .../rule/matchEngine/TagMatchEngine.hpp | 16 + .../rule/matchEngine/WorkspaceMatchEngine.cpp | 12 + .../rule/matchEngine/WorkspaceMatchEngine.hpp | 16 + src/desktop/rule/utils/SetUtils.hpp | 17 + src/desktop/rule/windowRule/WindowRule.cpp | 186 +++++ src/desktop/rule/windowRule/WindowRule.hpp | 37 + .../rule/windowRule/WindowRuleApplicator.cpp | 642 +++++++++++++++ .../rule/windowRule/WindowRuleApplicator.hpp | 148 ++++ .../windowRule/WindowRuleEffectContainer.cpp | 76 ++ .../windowRule/WindowRuleEffectContainer.hpp | 79 ++ src/desktop/types/OverridableVar.hpp | 153 ++++ src/events/Windows.cpp | 401 +++------- src/helpers/MiscFunctions.cpp | 10 + src/helpers/MiscFunctions.hpp | 1 + src/helpers/Monitor.cpp | 6 +- src/helpers/TagKeeper.cpp | 6 +- src/helpers/TagKeeper.hpp | 4 +- src/helpers/math/Expression.cpp | 22 + src/helpers/math/Expression.hpp | 28 + src/helpers/varlist/VarList.hpp | 1 + src/layout/DwindleLayout.cpp | 24 +- src/layout/IHyprLayout.cpp | 63 +- src/layout/MasterLayout.cpp | 20 +- src/managers/KeybindManager.cpp | 230 ++++-- src/managers/KeybindManager.hpp | 2 +- src/managers/animation/AnimationManager.cpp | 4 +- .../animation/DesktopAnimationManager.cpp | 6 +- src/managers/input/IdleInhibitor.cpp | 6 +- src/managers/input/InputManager.cpp | 2 +- .../input/trackpad/gestures/CloseGesture.cpp | 2 +- src/protocols/Screencopy.cpp | 6 +- src/protocols/ShortcutsInhibit.cpp | 2 +- src/protocols/ToplevelExport.cpp | 4 +- src/protocols/XDGDialog.cpp | 2 +- src/protocols/XDGTag.cpp | 3 + src/protocols/types/ContentType.cpp | 10 +- src/protocols/types/ContentType.hpp | 1 + src/render/OpenGL.cpp | 16 +- src/render/Renderer.cpp | 36 +- .../decorations/CHyprBorderDecoration.cpp | 3 +- .../decorations/CHyprDropShadowDecoration.cpp | 4 +- .../decorations/CHyprGroupBarDecoration.cpp | 2 +- 93 files changed, 3574 insertions(+), 2255 deletions(-) delete mode 100644 src/desktop/LayerRule.cpp delete mode 100644 src/desktop/LayerRule.hpp delete mode 100644 src/desktop/Rule.cpp delete mode 100644 src/desktop/Rule.hpp delete mode 100644 src/desktop/WindowOverridableVar.hpp delete mode 100644 src/desktop/WindowRule.cpp delete mode 100644 src/desktop/WindowRule.hpp create mode 100644 src/desktop/rule/Engine.cpp create mode 100644 src/desktop/rule/Engine.hpp create mode 100644 src/desktop/rule/Rule.cpp create mode 100644 src/desktop/rule/Rule.hpp create mode 100644 src/desktop/rule/effect/EffectContainer.hpp create mode 100644 src/desktop/rule/layerRule/LayerRule.cpp create mode 100644 src/desktop/rule/layerRule/LayerRule.hpp create mode 100644 src/desktop/rule/layerRule/LayerRuleApplicator.cpp create mode 100644 src/desktop/rule/layerRule/LayerRuleApplicator.hpp create mode 100644 src/desktop/rule/layerRule/LayerRuleEffectContainer.cpp create mode 100644 src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp create mode 100644 src/desktop/rule/matchEngine/BoolMatchEngine.cpp create mode 100644 src/desktop/rule/matchEngine/BoolMatchEngine.hpp create mode 100644 src/desktop/rule/matchEngine/IntMatchEngine.cpp create mode 100644 src/desktop/rule/matchEngine/IntMatchEngine.hpp create mode 100644 src/desktop/rule/matchEngine/MatchEngine.cpp create mode 100644 src/desktop/rule/matchEngine/MatchEngine.hpp create mode 100644 src/desktop/rule/matchEngine/RegexMatchEngine.cpp create mode 100644 src/desktop/rule/matchEngine/RegexMatchEngine.hpp create mode 100644 src/desktop/rule/matchEngine/TagMatchEngine.cpp create mode 100644 src/desktop/rule/matchEngine/TagMatchEngine.hpp create mode 100644 src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp create mode 100644 src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp create mode 100644 src/desktop/rule/utils/SetUtils.hpp create mode 100644 src/desktop/rule/windowRule/WindowRule.cpp create mode 100644 src/desktop/rule/windowRule/WindowRule.hpp create mode 100644 src/desktop/rule/windowRule/WindowRuleApplicator.cpp create mode 100644 src/desktop/rule/windowRule/WindowRuleApplicator.hpp create mode 100644 src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp create mode 100644 src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp create mode 100644 src/desktop/types/OverridableVar.hpp create mode 100644 src/helpers/math/Expression.cpp create mode 100644 src/helpers/math/Expression.hpp diff --git a/.github/actions/setup_base/action.yml b/.github/actions/setup_base/action.yml index d7b52a79..665d7f07 100644 --- a/.github/actions/setup_base/action.yml +++ b/.github/actions/setup_base/action.yml @@ -45,6 +45,7 @@ runs: libxkbfile \ lld \ meson \ + muparser \ ninja \ pango \ pixman \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 014f386a..ee0c34a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,7 +233,8 @@ pkg_check_modules( libinput>=1.28 gbm gio-2.0 - re2) + re2 + muparser) find_package(hyprwayland-scanner 0.3.10 REQUIRED) diff --git a/example/hyprland.conf b/example/hyprland.conf index a1408dc3..1bccaa2a 100644 --- a/example/hyprland.conf +++ b/example/hyprland.conf @@ -159,10 +159,23 @@ animations { # uncomment all if you wish to use that. # workspace = w[tv1], gapsout:0, gapsin:0 # workspace = f[1], gapsout:0, gapsin:0 -# windowrule = bordersize 0, floating:0, onworkspace:w[tv1] -# windowrule = rounding 0, floating:0, onworkspace:w[tv1] -# windowrule = bordersize 0, floating:0, onworkspace:f[1] -# windowrule = rounding 0, floating:0, onworkspace:f[1] +# windowrule { +# name = smart-gaps-1 +# floating = false +# on_workspace = n[s:window] w[tv1] +# +# border_size = 0 +# rounding = 0 +# } +# +# windowrule { +# name = smart-gaps-2 +# floating = false +# on_workspace = n[s:window] f[1] +# +# border_size = 0 +# rounding = 0 +# } # See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more dwindle { @@ -294,11 +307,25 @@ bindl = , XF86AudioPrev, exec, playerctl previous # See https://wiki.hypr.land/Configuring/Window-Rules/ for more # See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules -# Example windowrule -# windowrule = float,class:^(kitty)$,title:^(kitty)$ +# Example windowrules that are useful -# Ignore maximize requests from apps. You'll probably like this. -windowrule = suppressevent maximize, class:.* +windowrule { + # Ignore maximize requests from all apps. You'll probably like this. + name = suppress-maximize-events + match:class = .* -# Fix some dragging issues with XWayland -windowrule = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 + suppress_event = maximize +} + +windowrule { + # Fix some dragging issues with XWayland + match:name = fix-xwayland-drags + match:class = ^$ + match:title = ^$ + match:xwayland = true + match:float = true + match:fullscreen = false + match:pin = false + + no_focus = true +} diff --git a/hyprtester/plugin/src/main.cpp b/hyprtester/plugin/src/main.cpp index 1d0b68dc..72120eac 100644 --- a/hyprtester/plugin/src/main.cpp +++ b/hyprtester/plugin/src/main.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #undef private @@ -245,6 +247,30 @@ static SDispatchResult keybind(std::string in) { return {}; } +static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0; + +// +static SDispatchResult addRule(std::string in) { + ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); + + if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX) + return {.success = false, .error = "re-registering returned a different id?"}; + return {}; +} + +static SDispatchResult checkRule(std::string in) { + if (!g_pCompositor->m_lastWindow) + return {.success = false, .error = "No window"}; + + if (!g_pCompositor->m_lastWindow->m_ruleApplicator->m_otherProps.props.contains(ruleIDX)) + return {.success = false, .error = "No rule"}; + + if (g_pCompositor->m_lastWindow->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect") + return {.success = false, .error = "Effect isn't \"effect\""}; + + return {}; +} + APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; @@ -255,6 +281,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:gesture", ::simulateGesture); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule); // init mouse g_mouse = CTestMouse::create(false); diff --git a/hyprtester/src/tests/clients/pointer-scroll.cpp b/hyprtester/src/tests/clients/pointer-scroll.cpp index e1ba237f..2ea93a14 100644 --- a/hyprtester/src/tests/clients/pointer-scroll.cpp +++ b/hyprtester/src/tests/clients/pointer-scroll.cpp @@ -64,7 +64,7 @@ static bool startClient(SClient& client) { // wait for window to appear std::this_thread::sleep_for(std::chrono::milliseconds(5000)); - if (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") { + if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") { NLog::log("{}Failed to disable animations for client window", Colors::RED, ret); return false; } @@ -130,7 +130,7 @@ static bool test() { EXPECT(sendScroll(10), true); EXPECT(getLastDelta(client), 30); - EXPECT(getFromSocket("r/dispatch setprop active scrollmouse 4"), "ok"); + EXPECT(getFromSocket("r/dispatch setprop active scroll_mouse 4"), "ok"); EXPECT(sendScroll(10), true); EXPECT(getLastDelta(client), 40); diff --git a/hyprtester/src/tests/clients/pointer-warp.cpp b/hyprtester/src/tests/clients/pointer-warp.cpp index f37b94c3..bb03afd2 100644 --- a/hyprtester/src/tests/clients/pointer-warp.cpp +++ b/hyprtester/src/tests/clients/pointer-warp.cpp @@ -64,7 +64,7 @@ static bool startClient(SClient& client) { // wait for window to appear std::this_thread::sleep_for(std::chrono::milliseconds(5000)); - if (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") { + if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") { NLog::log("{}Failed to disable animations for client window", Colors::RED, ret); return false; } diff --git a/hyprtester/src/tests/main/hyprctl.cpp b/hyprtester/src/tests/main/hyprctl.cpp index 95f7caae..e8759d28 100644 --- a/hyprtester/src/tests/main/hyprctl.cpp +++ b/hyprtester/src/tests/main/hyprctl.cpp @@ -56,82 +56,82 @@ static bool testGetprop() { return false; } - // animationstyle - EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "(unset)"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": ""})"); - getFromSocket("/dispatch setprop class:kitty animationstyle teststyle"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "teststyle"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": "teststyle"})"); + // animation + EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "(unset)"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": ""})"); + getFromSocket("/dispatch setprop class:kitty animation teststyle"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "teststyle"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": "teststyle"})"); - // maxsize - EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "inf inf"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [null,null]})"); - getFromSocket("/dispatch setprop class:kitty maxsize 200 150"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "200 150"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [200,150]})"); + // max_size + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "inf inf"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [null,null]})"); + getFromSocket("/dispatch setprop class:kitty max_size 200 150"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "200 150"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [200,150]})"); - // minsize - EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "20 20"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [20,20]})"); - getFromSocket("/dispatch setprop class:kitty minsize 100 50"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "100 50"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [100,50]})"); + // min_size + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "20 20"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [20,20]})"); + getFromSocket("/dispatch setprop class:kitty min_size 100 50"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})"); - // alpha - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "1"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 1})"); - getFromSocket("/dispatch setprop class:kitty alpha 0.3"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "0.3"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 0.3})"); + // opacity + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})"); + getFromSocket("/dispatch setprop class:kitty opacity 0.3"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "0.3"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 0.3})"); - // alphainactive - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "1"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 1})"); - getFromSocket("/dispatch setprop class:kitty alphainactive 0.5"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "0.5"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 0.5})"); + // opacity_inactive + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "1"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 1})"); + getFromSocket("/dispatch setprop class:kitty opacity_inactive 0.5"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "0.5"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 0.5})"); - // alphafullscreen - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "1"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 1})"); - getFromSocket("/dispatch setprop class:kitty alphafullscreen 0.75"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "0.75"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 0.75})"); + // opacity_fullscreen + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "1"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 1})"); + getFromSocket("/dispatch setprop class:kitty opacity_fullscreen 0.75"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "0.75"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 0.75})"); - // alphaoverride - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "false"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": false})"); - getFromSocket("/dispatch setprop class:kitty alphaoverride true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": true})"); + // opacity_override + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "false"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": false})"); + getFromSocket("/dispatch setprop class:kitty opacity_override true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": true})"); - // alphainactiveoverride - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "false"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": false})"); - getFromSocket("/dispatch setprop class:kitty alphainactiveoverride true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": true})"); + // opacity_inactive_override + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "false"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": false})"); + getFromSocket("/dispatch setprop class:kitty opacity_inactive_override true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": true})"); - // alphafullscreenoverride - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "false"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": false})"); - getFromSocket("/dispatch setprop class:kitty alphafullscreenoverride true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": true})"); + // opacity_fullscreen_override + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "false"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": false})"); + getFromSocket("/dispatch setprop class:kitty opacity_fullscreen_override true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": true})"); - // activebordercolor - EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ee33ccff ee00ff99 45deg"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ee33ccff ee00ff99 45deg"})"); - getFromSocket("/dispatch setprop class:kitty activebordercolor rgb(abcdef)"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ffabcdef 0deg"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ffabcdef 0deg"})"); + // active_border_color + EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ee33ccff ee00ff99 45deg"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ee33ccff ee00ff99 45deg"})"); + getFromSocket("/dispatch setprop class:kitty active_border_color rgb(abcdef)"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ffabcdef 0deg"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ffabcdef 0deg"})"); // bool window properties - EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput"), "false"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": false})"); - getFromSocket("/dispatch setprop class:kitty allowsinput true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput"), "true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": true})"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "false"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": false})"); + getFromSocket("/dispatch setprop class:kitty allows_input true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": true})"); // int window properties EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "10"); @@ -141,16 +141,16 @@ static bool testGetprop() { EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 4})"); // float window properties - EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "2"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 2})"); - getFromSocket("/dispatch setprop class:kitty roundingpower 1.25"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "1.25"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 1.25})"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "2"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 2})"); + getFromSocket("/dispatch setprop class:kitty rounding_power 1.25"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "1.25"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 1.25})"); // errors EXPECT(getCommandStdOut("hyprctl getprop"), "not enough args"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty"), "not enough args"); - EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animationstyle"), "window not found"); + EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animation"), "window not found"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty nonexistantprop"), "prop not found"); // kill all diff --git a/hyprtester/src/tests/main/tags.cpp b/hyprtester/src/tests/main/tags.cpp index 22bedcde..c345fe71 100644 --- a/hyprtester/src/tests/main/tags.cpp +++ b/hyprtester/src/tests/main/tags.cpp @@ -21,21 +21,24 @@ static bool testTags() { NLog::log("{}Testing testTag tags", Colors::YELLOW); - OK(getFromSocket("/keyword windowrule tag +testTag, class:tagged")); - OK(getFromSocket("/keyword windowrule noshadow, tag:negative:testTag")); - OK(getFromSocket("/keyword windowrule noborder, tag:testTag")); + OK(getFromSocket("/keyword windowrule[tag-test-1]:tag +testTag")); + OK(getFromSocket("/keyword windowrule[tag-test-1]:match:class tagged")); + OK(getFromSocket("/keyword windowrule[tag-test-2]:match:tag negative:testTag")); + OK(getFromSocket("/keyword windowrule[tag-test-2]:no_shadow true")); + OK(getFromSocket("/keyword windowrule[tag-test-3]:match:tag testTag")); + OK(getFromSocket("/keyword windowrule[tag-test-3]:no_dim true")); EXPECT(Tests::windowCount(), 2); OK(getFromSocket("/dispatch focuswindow class:tagged")); - NLog::log("{}Testing tagged window for noborder & noshadow", Colors::YELLOW); + NLog::log("{}Testing tagged window for no_dim 0 & no_shadow", Colors::YELLOW); EXPECT_CONTAINS(getFromSocket("/activewindow"), "testTag"); - EXPECT_CONTAINS(getFromSocket("/getprop activewindow noborder"), "true"); - EXPECT_CONTAINS(getFromSocket("/getprop activewindow noshadow"), "false"); - NLog::log("{}Testing untagged window for noborder & noshadow", Colors::YELLOW); + EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "true"); + EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "false"); + NLog::log("{}Testing untagged window for no_dim & no_shadow", Colors::YELLOW); OK(getFromSocket("/dispatch focuswindow class:untagged")); EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "testTag"); - EXPECT_CONTAINS(getFromSocket("/getprop activewindow noborder"), "false"); - EXPECT_CONTAINS(getFromSocket("/getprop activewindow noshadow"), "true"); + EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "true"); + EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "false"); Tests::killAllWindows(); EXPECT(Tests::windowCount(), 0); diff --git a/hyprtester/src/tests/main/window.cpp b/hyprtester/src/tests/main/window.cpp index 6cfa061c..2cf42eef 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -246,12 +246,15 @@ static bool test() { testSwapWindow(); + getFromSocket("/dispatch workspace 1"); + NLog::log("{}Testing minsize/maxsize rules for tiled windows", Colors::YELLOW); { // Enable the config for testing, test max/minsize for tiled windows and centering OK(getFromSocket("/keyword misc:size_limits_tiled 1")); - OK(getFromSocket("/keyword windowrule maxsize 1500 500, class:kitty_maxsize")); - OK(getFromSocket("/keyword windowrule minsize 1200 500, class:kitty_maxsize")); + OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize")); + OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1500 500")); + OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500")); if (!spawnKitty("kitty_maxsize")) return false; @@ -297,29 +300,127 @@ static bool test() { EXPECT_CONTAINS(str, "floating: 1"); EXPECT_CONTAINS(str, std::format("size: {},{}", SIZE, SIZE)); EXPECT_NOT_CONTAINS(str, "pinned: 1"); - OK(getFromSocket("/keyword windowrule plugin:someplugin:variable, class:wr_kitty")); - OK(getFromSocket("/keyword windowrule plugin:someplugin:variable 10, class:wr_kitty")); - OK(getFromSocket("/keyword windowrule workspace 1, class:wr_kitty")); - OK(getFromSocket("/keyword windowrule workspace special:magic, class:magic_kitty")); + } - if (!spawnKitty("magic_kitty")) - return false; - EXPECT_CONTAINS(getFromSocket("/activewindow"), "special:magic"); + OK(getFromSocket("/keyword windowrule[wr-kitty-stuff]:opacity 0.5 0.5 override")); + + { + auto str = getFromSocket("/getprop active opacity"); + EXPECT_CONTAINS(str, "0.5"); + } + + OK(getFromSocket("/keyword windowrule[special-magic-kitty]:match:class magic_kitty")); + OK(getFromSocket("/keyword windowrule[special-magic-kitty]:workspace special:magic")); + + if (!spawnKitty("magic_kitty")) + return false; + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "special:magic"); EXPECT_NOT_CONTAINS(str, "workspace: 9"); } - NLog::log("{}Testing faulty rules", Colors::YELLOW); - { - const auto PARAM = "Invalid parameter"; - const auto RULE = "Invalid value"; - const auto NORULE = "no rules provided"; - EXPECT_CONTAINS(getFromSocket("/keyword windowrule notarule, class:wr_kitty"), RULE) - EXPECT_CONTAINS(getFromSocket("/keyword windowrule class:wr_kitty"), NORULE) - EXPECT_CONTAINS(getFromSocket("/keyword windowrule float, class:wr_kitty, size"), PARAM) - EXPECT_CONTAINS(getFromSocket("/keyword windowrule float, classI:wr_kitty"), PARAM) - EXPECT_CONTAINS(getFromSocket("/keyword windowrule workspace:, class:wr_kitty"), NORULE) + if (auto str = getFromSocket("/monitors"); str.contains("magic)")) { + OK(getFromSocket("/dispatch togglespecialworkspace magic")); } + Tests::killAllWindows(); + + if (!spawnKitty("tag_kitty")) + return false; + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "floating: 1"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + // test rules that overlap effects but don't overlap props + OK(getFromSocket("/keyword windowrule match:class overlap_kitty, border_size 0")); + OK(getFromSocket("/keyword windowrule match:fullscreen false, border_size 10")); + + if (!spawnKitty("overlap_kitty")) + return false; + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "10"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + OK(getFromSocket("/keyword general:border_size 0")); + OK(getFromSocket("/keyword windowrule match:float true, border_size 10")); + + if (!spawnKitty("border_kitty")) + return false; + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "0"); + } + + OK(getFromSocket("/dispatch togglefloating")); + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "10"); + } + + OK(getFromSocket("/dispatch togglefloating")); + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "0"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + // test expression rules + OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, move 20+(monitor_w*0.1) monitor_h*0.5")); + + if (!spawnKitty("expr_kitty")) + return false; + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "floating: 1"); + EXPECT_CONTAINS(str, "at: 212,540"); + EXPECT_CONTAINS(str, "size: 960,540"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + OK(getFromSocket("/dispatch plugin:test:add_rule")); + OK(getFromSocket("/reload")); + + OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect")); + + if (!spawnKitty("plugin_kitty")) + return false; + + OK(getFromSocket("/dispatch plugin:test:check_rule")); + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + OK(getFromSocket("/dispatch plugin:test:add_rule")); + OK(getFromSocket("/reload")); + + OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty")); + OK(getFromSocket("/keyword windowrule[test-plugin-rule]:plugin_rule effect")); + + if (!spawnKitty("plugin_kitty")) + return false; + + OK(getFromSocket("/dispatch plugin:test:check_rule")); + NLog::log("{}Reloading config", Colors::YELLOW); OK(getFromSocket("/reload")); diff --git a/hyprtester/src/tests/main/workspaces.cpp b/hyprtester/src/tests/main/workspaces.cpp index def35d08..9d380281 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -27,7 +27,7 @@ static bool test() { // test on workspace "window" NLog::log("{}Switching to workspace 1", Colors::YELLOW); - OK(getFromSocket("/dispatch workspace 1")); + getFromSocket("/dispatch workspace 1"); NLog::log("{}Checking persistent no-mon", Colors::YELLOW); OK(getFromSocket("r/keyword workspace 966,persistent:1")); diff --git a/hyprtester/test.conf b/hyprtester/test.conf index 047001c2..ac28bc5a 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -318,28 +318,70 @@ submap = reset ### WINDOWS AND WORKSPACES ### ############################## -# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more -# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules +windowrule { + # Ignore maximize requests from apps. You'll probably like this. + name = suppress-maximize-events + match:class = .* -# Example windowrule v1 -# windowrule = float, ^(kitty)$ + suppress_event = maximize +} -# Example windowrule v2 -# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$ +windowrule { + # Fix some dragging issues with XWayland + name = fix-xwayland-drags + match:class = ^$ + match:title = ^$ + match:xwayland = true + match:float = true + match:fullscreen = false + match:pin = false -# Ignore maximize requests from apps. You'll probably like this. -windowrulev2 = suppressevent maximize, class:.* + no_focus = true +} -# Fix some dragging issues with XWayland -windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 - -# Workspace "windows" is a smart gaps one workspace = n[s:window] w[tv1], gapsout:0, gapsin:0 workspace = n[s:window] f[1], gapsout:0, gapsin:0 -windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] w[tv1] -windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] w[tv1] -windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] f[1] -windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] f[1] + +windowrule { + name = smart-gaps-1 + match:float = false + match:workspace = n[s:window] w[tv1] + + border_size = 0 + rounding = 0 +} + +windowrule { + name = smart-gaps-2 + match:float = false + match:workspace = n[s:window] f[1] + + border_size = 0 + rounding = 0 +} + +windowrule { + name = wr-kitty-stuff + match:class = wr_kitty + + float = true + size = 200 200 + pin = false +} + +windowrule { + name = tagged-kitty-floats + match:tag = tag_kitty + + float = true +} + +windowrule { + name = static-kitty-tag + match:class = tag_kitty + + tag = +tag_kitty +} gesture = 3, left, dispatcher, exec, kitty gesture = 3, right, float @@ -356,7 +398,3 @@ gesture = 5, left, dispatcher, sendshortcut, , i, activewindow gesture = 5, right, dispatcher, sendshortcut, , t, activewindow gesture = 4, right, dispatcher, sendshortcut, , return, activewindow gesture = 4, left, dispatcher, movecursortocorner, 1 - -windowrule = float, pin, class:wr_kitty -windowrule = size 200 200, class:wr_kitty -windowrule = unset pin, class:wr_kitty diff --git a/nix/default.nix b/nix/default.nix index 867b5b0c..45fd273b 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -26,6 +26,7 @@ libxkbcommon, libuuid, libgbm, + muparser, pango, pciutils, re2, @@ -149,6 +150,7 @@ in libuuid libxkbcommon libgbm + muparser pango pciutils re2 diff --git a/src/Compositor.cpp b/src/Compositor.cpp index a7f26ba6..0f24a8bf 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -901,8 +901,8 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper if (ONLY_PRIORITY && !w->priorityFocus()) continue; - if (w->m_isFloating && w->m_isMapped && !w->isHidden() && !w->m_X11ShouldntFocus && w->m_pinned && !w->m_windowData.noFocus.valueOrDefault() && w != pIgnoreWindow && - !isShadowedByModal(w)) { + if (w->m_isFloating && w->m_isMapped && !w->isHidden() && !w->m_X11ShouldntFocus && w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() && + w != pIgnoreWindow && !isShadowedByModal(w)) { const auto BB = w->getWindowBoxUnified(properties); CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0); if (box.containsPoint(g_pPointerManager->position())) @@ -939,7 +939,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper continue; } - if (w->m_isFloating && w->m_isMapped && w->m_workspace->isVisible() && !w->isHidden() && !w->m_pinned && !w->m_windowData.noFocus.valueOrDefault() && + if (w->m_isFloating && w->m_isMapped && w->m_workspace->isVisible() && !w->isHidden() && !w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pIgnoreWindow && (!aboveFullscreen || w->m_createdOverFullscreen) && !isShadowedByModal(w)) { // OR windows should add focus to parent if (w->m_X11ShouldntFocus && !w->isX11OverrideRedirect()) @@ -1000,7 +1000,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper continue; if (!w->m_isX11 && !w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && - !w->m_windowData.noFocus.valueOrDefault() && w != pIgnoreWindow && !isShadowedByModal(w)) { + !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pIgnoreWindow && !isShadowedByModal(w)) { if (w->hasPopupAt(pos)) return w; } @@ -1016,7 +1016,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper if (!w->m_workspace) continue; - if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_windowData.noFocus.valueOrDefault() && + if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pIgnoreWindow && !isShadowedByModal(w)) { CBox box = (properties & USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_position, w->m_size}; if (box.containsPoint(pos)) @@ -1152,7 +1152,7 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface m_lastWindow.reset(); if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { - updateWindowAnimatedDecorationValues(PLASTWINDOW); + PLASTWINDOW->updateDecorationValues(); g_pXWaylandManager->activateWindow(PLASTWINDOW, false); } @@ -1172,7 +1172,7 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface return; } - if (pWindow->m_windowData.noFocus.valueOrDefault()) { + if (pWindow->m_ruleApplicator->noFocus().valueOrDefault()) { Debug::log(LOG, "Ignoring focus to nofocus window!"); return; } @@ -1209,9 +1209,9 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface // we need to make the PLASTWINDOW not equal to m_pLastWindow so that RENDERDATA is correct for an unfocused window if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { - PLASTWINDOW->updateDynamicRules(); + PLASTWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FOCUS); - updateWindowAnimatedDecorationValues(PLASTWINDOW); + PLASTWINDOW->updateDecorationValues(); if (!pWindow->m_isX11 || !pWindow->isX11OverrideRedirect()) g_pXWaylandManager->activateWindow(PLASTWINDOW, false); @@ -1225,10 +1225,10 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface g_pXWaylandManager->activateWindow(pWindow, true); // sets the m_pLastWindow - pWindow->updateDynamicRules(); + pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FOCUS); pWindow->onFocusAnimUpdate(); - updateWindowAnimatedDecorationValues(pWindow); + pWindow->updateDecorationValues(); if (pWindow->m_isUrgent) pWindow->m_isUrgent = false; @@ -1334,7 +1334,7 @@ SP CCompositor::vectorToLayerSurface(const Vector2D& pos, st for (auto const& ls : *layerSurfaces | std::views::reverse) { if (!ls->m_mapped || ls->m_fadingOut || !ls->m_layerSurface || (ls->m_layerSurface && !ls->m_layerSurface->m_surface->m_mapped) || ls->m_alpha->value() == 0.f || - (aboveLockscreen && (!ls->m_aboveLockscreen || !ls->m_aboveLockscreenInteractable))) + (aboveLockscreen && ls->m_ruleApplicator->aboveLock().valueOrDefault() != 2)) continue; auto [surf, local] = ls->m_layerSurface->m_surface->at(pos - ls->m_geometry.pos(), true); @@ -1715,7 +1715,7 @@ static bool isFloatingMatches(WINDOWPTR w, std::optional floating) { template static bool isWindowAvailableForCycle(WINDOWPTR pWindow, WINDOWPTR w, bool focusableOnly, std::optional floating, bool anyWorkspace = false) { return isFloatingMatches(w, floating) && - (w != pWindow && isWorkspaceMatches(pWindow, w, anyWorkspace) && w->m_isMapped && !w->isHidden() && (!focusableOnly || !w->m_windowData.noFocus.valueOrDefault())); + (w != pWindow && isWorkspaceMatches(pWindow, w, anyWorkspace) && w->m_isMapped && !w->isHidden() && (!focusableOnly || !w->m_ruleApplicator->noFocus().valueOrDefault())); } template @@ -1906,103 +1906,10 @@ void CCompositor::updateAllWindowsAnimatedDecorationValues() { if (!w->m_isMapped) continue; - updateWindowAnimatedDecorationValues(w); + w->updateDecorationValues(); } } -void CCompositor::updateWindowAnimatedDecorationValues(PHLWINDOW pWindow) { - // optimization - static auto PACTIVECOL = CConfigValue("general:col.active_border"); - static auto PINACTIVECOL = CConfigValue("general:col.inactive_border"); - static auto PNOGROUPACTIVECOL = CConfigValue("general:col.nogroup_border_active"); - static auto PNOGROUPINACTIVECOL = CConfigValue("general:col.nogroup_border"); - static auto PGROUPACTIVECOL = CConfigValue("group:col.border_active"); - static auto PGROUPINACTIVECOL = CConfigValue("group:col.border_inactive"); - static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); - static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); - static auto PINACTIVEALPHA = CConfigValue("decoration:inactive_opacity"); - static auto PACTIVEALPHA = CConfigValue("decoration:active_opacity"); - static auto PFULLSCREENALPHA = CConfigValue("decoration:fullscreen_opacity"); - static auto PSHADOWCOL = CConfigValue("decoration:shadow:color"); - static auto PSHADOWCOLINACTIVE = CConfigValue("decoration:shadow:color_inactive"); - static auto PDIMSTRENGTH = CConfigValue("decoration:dim_strength"); - static auto PDIMENABLED = CConfigValue("decoration:dim_inactive"); - static auto PDIMMODAL = CConfigValue("decoration:dim_modal"); - - auto* const ACTIVECOL = sc((PACTIVECOL.ptr())->getData()); - auto* const INACTIVECOL = sc((PINACTIVECOL.ptr())->getData()); - auto* const NOGROUPACTIVECOL = sc((PNOGROUPACTIVECOL.ptr())->getData()); - auto* const NOGROUPINACTIVECOL = sc((PNOGROUPINACTIVECOL.ptr())->getData()); - auto* const GROUPACTIVECOL = sc((PGROUPACTIVECOL.ptr())->getData()); - auto* const GROUPINACTIVECOL = sc((PGROUPINACTIVECOL.ptr())->getData()); - auto* const GROUPACTIVELOCKEDCOL = sc((PGROUPACTIVELOCKEDCOL.ptr())->getData()); - auto* const GROUPINACTIVELOCKEDCOL = sc((PGROUPINACTIVELOCKEDCOL.ptr())->getData()); - - auto setBorderColor = [&](CGradientValueData grad) -> void { - if (grad == pWindow->m_realBorderColor) - return; - - pWindow->m_realBorderColorPrevious = pWindow->m_realBorderColor; - pWindow->m_realBorderColor = grad; - pWindow->m_borderFadeAnimationProgress->setValueAndWarp(0.f); - *pWindow->m_borderFadeAnimationProgress = 1.f; - }; - - const bool IS_SHADOWED_BY_MODAL = pWindow->m_xdgSurface && pWindow->m_xdgSurface->m_toplevel && pWindow->m_xdgSurface->m_toplevel->anyChildModal(); - - // border - const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(pWindow); - if (RENDERDATA.isBorderGradient) - setBorderColor(*RENDERDATA.borderGradient); - else { - const bool GROUPLOCKED = pWindow->m_groupData.pNextWindow.lock() ? pWindow->getGroupHead()->m_groupData.locked : false; - if (pWindow == m_lastWindow) { - const auto* const ACTIVECOLOR = - !pWindow->m_groupData.pNextWindow.lock() ? (!pWindow->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); - setBorderColor(pWindow->m_windowData.activeBorderColor.valueOr(*ACTIVECOLOR)); - } else { - const auto* const INACTIVECOLOR = !pWindow->m_groupData.pNextWindow.lock() ? (!pWindow->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : - (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); - setBorderColor(pWindow->m_windowData.inactiveBorderColor.valueOr(*INACTIVECOLOR)); - } - } - - // opacity - const auto PWORKSPACE = pWindow->m_workspace; - if (pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) { - *pWindow->m_activeInactiveAlpha = pWindow->m_windowData.alphaFullscreen.valueOrDefault().applyAlpha(*PFULLSCREENALPHA); - } else { - if (pWindow == m_lastWindow) - *pWindow->m_activeInactiveAlpha = pWindow->m_windowData.alpha.valueOrDefault().applyAlpha(*PACTIVEALPHA); - else - *pWindow->m_activeInactiveAlpha = pWindow->m_windowData.alphaInactive.valueOrDefault().applyAlpha(*PINACTIVEALPHA); - } - - // dim - float goalDim = 1.F; - if (pWindow == m_lastWindow.lock() || pWindow->m_windowData.noDim.valueOrDefault() || !*PDIMENABLED) - goalDim = 0; - else - goalDim = *PDIMSTRENGTH; - - if (IS_SHADOWED_BY_MODAL && *PDIMMODAL) - goalDim += (1.F - goalDim) / 2.F; - - *pWindow->m_dimPercent = goalDim; - - // shadow - if (!pWindow->isX11OverrideRedirect() && !pWindow->m_X11DoesntWantBorders) { - if (pWindow == m_lastWindow) - *pWindow->m_realShadowColor = CHyprColor(*PSHADOWCOL); - else - *pWindow->m_realShadowColor = CHyprColor(*PSHADOWCOLINACTIVE != -1 ? *PSHADOWCOLINACTIVE : *PSHADOWCOL); - } else { - pWindow->m_realShadowColor->setValueAndWarp(CHyprColor(0, 0, 0, 0)); // no shadow - } - - pWindow->updateWindowDecos(); -} - MONITORID CCompositor::getNextAvailableMonitorID(std::string const& name) { // reuse ID if it's already in the map, and the monitor with that ID is not being used by another monitor if (m_monitorIDMap.contains(name) && !std::ranges::any_of(m_realMonitors, [&](auto m) { return m->m_id == m_monitorIDMap[name]; })) @@ -2341,14 +2248,14 @@ void CCompositor::changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, cons } void CCompositor::setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) { - if (PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) + if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); else setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = PWINDOW->m_fullscreenState.client}); } void CCompositor::setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) { - if (PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) + if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); else setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = PWINDOW->m_fullscreenState.internal, .client = MODE}); @@ -2389,15 +2296,16 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS } // TODO: update the state on syncFullscreen changes - if (!CHANGEINTERNAL && PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) + if (!CHANGEINTERNAL && PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) return; PWINDOW->m_fullscreenState.client = state.client; g_pXWaylandManager->setWindowFullscreen(PWINDOW, state.client & FSMODE_FULLSCREEN); if (!CHANGEINTERNAL) { - PWINDOW->updateDynamicRules(); - updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | + Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + PWINDOW->updateDecorationValues(); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); return; } @@ -2411,8 +2319,10 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc(EFFECTIVE_MODE) != FSMODE_NONE)}); EMIT_HOOK_EVENT("fullscreen", PWINDOW); - PWINDOW->updateDynamicRules(); - updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | + Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + + PWINDOW->updateDecorationValues(); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); // make all windows on the same workspace under the fullscreen window @@ -2552,7 +2462,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) { } case MODE_TAG_REGEX: { bool tagMatched = false; - for (auto const& t : w->m_tags.getTags()) { + for (auto const& t : w->m_ruleApplicator->m_tagKeeper.getTags()) { if (RE2::FullMatch(t, regexCheck)) { tagMatched = true; break; @@ -2843,7 +2753,7 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor } pWindow->updateToplevel(); - pWindow->updateDynamicRules(); + pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); pWindow->uncacheWindowDecos(); pWindow->updateGroupOutputs(); @@ -2874,7 +2784,7 @@ PHLWINDOW CCompositor::getForceFocus() { if (!w->m_isMapped || w->isHidden() || !w->m_workspace || !w->m_workspace->isVisible()) continue; - if (!w->m_stayFocused) + if (!w->m_ruleApplicator->stayFocused().valueOrDefault()) continue; return w; diff --git a/src/Compositor.hpp b/src/Compositor.hpp index bf6401e5..30b0f1bd 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -129,7 +129,6 @@ class CCompositor { PHLMONITOR getMonitorInDirection(const char&); PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&); void updateAllWindowsAnimatedDecorationValues(); - void updateWindowAnimatedDecorationValues(PHLWINDOW); MONITORID getNextAvailableMonitorID(std::string const& name); void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false); void swapActiveWorkspaces(PHLMONITOR, PHLMONITOR); diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 5e503451..f819e293 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -15,12 +15,6 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_INT, .data = SConfigOptionDescription::SRangeData{1, 0, 20}, }, - SConfigOptionDescription{ - .value = "general:no_border_on_floating", - .description = "disable borders for floating windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, SConfigOptionDescription{ .value = "general:gaps_in", .description = "gaps between windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)", diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index b4b6ed3d..8efbe945 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -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(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 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 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 CConfigManager::resetHLConfig() { const auto RET = verifyConfigExists(); + reloadRuleConfigs(); + return RET; } @@ -1179,6 +1186,77 @@ Hyprlang::CParseResult CConfigManager::handleMonitorv2() { return result; } +std::optional 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(ENABLED->getValue()) == 0) + return std::nullopt; + + SP rule = makeShared(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(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(VAL->getValue())); + } + + Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); + return std::nullopt; +} + +std::optional 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(ENABLED->getValue()) != 0) + return std::nullopt; + + SP rule = makeShared(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(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(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{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> CConfigManager::getMatchingRules(PHLWINDOW pWindow, bool dynamic, bool shadowExec) { - if (!valid(pWindow)) - return std::vector>(); - - // if the window is unmapped, don't process exec rules yet. - shadowExec = shadowExec || !pWindow->m_isMapped; - - std::vector> 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 internalMode, clientMode; - - if (ARGS[0] == "*") - internalMode = std::nullopt; - else if (isNumber(ARGS[0])) - internalMode = sc(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(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 PIDs = {sc(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(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> CConfigManager::getMatchingRules(PHLLS pLS) { - std::vector> 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(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(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& 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 CConfigManager::handleUnbind(const std::string& comma return {}; } -std::optional CConfigManager::handleWindowRule(const std::string& command, const std::string& value) { - const auto VARLIST = CVarList(value, 0, ',', true); - - std::vector tokens; - std::unordered_map 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 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> 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(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(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 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(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(ls.get())) == matchName) - ls->m_forceBlur = forceBlur; - } else if (ls->m_namespace == matchName) - ls->m_forceBlur = forceBlur; - } - } - } -} - -std::optional 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 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 CConfigManager::handleGesture(const std::string& comm return std::nullopt; } +std::optional CConfigManager::handleWindowrule(const std::string& command, const std::string& value) { + CVarList2 data(std::string{value}, 0, ','); + + SP rule = makeShared(); + + 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{m_keywordRules.back()}); + + return std::nullopt; +} + +std::optional CConfigManager::handleLayerrule(const std::string& command, const std::string& value) { + CVarList2 data(std::string{value}, 0, ','); + + SP rule = makeShared(); + + 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& CConfigManager::getAllDescriptions() { return CONFIG_OPTIONS; } diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 7f32be41..599ee8e7 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -13,14 +13,12 @@ #include #include "../helpers/Monitor.hpp" #include "../desktop/Window.hpp" -#include "../desktop/LayerRule.hpp" #include "ConfigDataValues.hpp" #include "../SharedDefs.hpp" #include "../helpers/Color.hpp" #include "../desktop/DesktopTypes.hpp" #include "../helpers/memory/Memory.hpp" -#include "../desktop/WindowRule.hpp" #include "../managers/XWaylandManager.hpp" #include "../managers/KeybindManager.hpp" @@ -68,11 +66,6 @@ struct SPluginVariable { std::string name = ""; }; -struct SExecRequestedRule { - std::string szRule = ""; - uint64_t iPid = 0; -}; - enum eConfigOptionType : uint8_t { CONFIG_OPTION_BOOL = 0, CONFIG_OPTION_INT = 1, /* e.g. 0/1/2*/ @@ -214,7 +207,6 @@ class CConfigManager { bool deviceConfigExplicitlySet(const std::string&, const std::string&); bool deviceConfigExists(const std::string&); Hyprlang::CConfigValue* getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback); - bool shouldBlurLS(const std::string&); void* const* getConfigValuePtr(const std::string&); Hyprlang::CConfigValue* getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = ""); @@ -229,8 +221,6 @@ class CConfigManager { std::string getBoundMonitorStringForWS(const std::string&); const std::vector& getAllWorkspaceRules(); - std::vector> getMatchingRules(PHLWINDOW, bool dynamic = true, bool shadowExec = false); - std::vector> getMatchingRules(PHLLS); void ensurePersistentWorkspacesPresent(); const std::vector& getAllDescriptions(); @@ -260,8 +250,6 @@ class CConfigManager { SP getAnimationPropertyConfig(const std::string&); - void addExecRule(const SExecRequestedRule&); - void handlePluginLoads(); std::string getErrors(); @@ -274,22 +262,24 @@ class CConfigManager { std::optional handleMonitor(const std::string&, const std::string&); std::optional handleBind(const std::string&, const std::string&); std::optional handleUnbind(const std::string&, const std::string&); - std::optional handleWindowRule(const std::string&, const std::string&); - std::optional handleLayerRule(const std::string&, const std::string&); std::optional handleWorkspaceRules(const std::string&, const std::string&); std::optional handleBezier(const std::string&, const std::string&); std::optional handleAnimation(const std::string&, const std::string&); std::optional handleSource(const std::string&, const std::string&); std::optional handleSubmap(const std::string&, const std::string&); - std::optional handleBlurLS(const std::string&, const std::string&); std::optional handleBindWS(const std::string&, const std::string&); std::optional handleEnv(const std::string&, const std::string&); std::optional handlePlugin(const std::string&, const std::string&); std::optional handlePermission(const std::string&, const std::string&); std::optional handleGesture(const std::string&, const std::string&); + std::optional handleWindowrule(const std::string&, const std::string&); + std::optional handleLayerrule(const std::string&, const std::string&); std::optional handleMonitorv2(const std::string& output); Hyprlang::CParseResult handleMonitorv2(); + std::optional addRuleFromConfigKey(const std::string& name); + std::optional addLayerRuleFromConfigKey(const std::string& name); + Hyprlang::CParseResult reloadRules(); std::string m_configCurrentPath; @@ -310,19 +300,16 @@ class CConfigManager { SSubmap m_currentSubmap; - std::vector m_execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty - std::vector m_declaredPlugins; std::vector m_pluginKeywords; std::vector m_pluginVariables; + std::vector> m_keywordRules; + bool m_isFirstLaunch = true; // For exec-once std::vector m_monitorRules; std::vector m_workspaceRules; - std::vector> m_windowRules; - std::vector> m_layerRules; - std::vector m_blurLSNamespaces; bool m_firstExecDispatched = false; bool m_manualCrashInitiated = false; @@ -336,11 +323,11 @@ class CConfigManager { uint32_t m_configValueNumber = 0; // internal methods - void updateBlurredLS(const std::string&, const bool); void setDefaultAnimationVars(); std::optional resetHLConfig(); std::optional generateConfig(std::string configPath); std::optional verifyConfigExists(); + void reloadRuleConfigs(); void postConfigReload(const Hyprlang::CParseResult& result); SWorkspaceRule mergeWorkspaceRules(const SWorkspaceRule&, const SWorkspaceRule&); diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index b5de6503..82a69715 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -44,6 +44,7 @@ using namespace Hyprutils::OS; #include "config/ConfigManager.hpp" #include "helpers/MiscFunctions.hpp" #include "../desktop/LayerSurface.hpp" +#include "../desktop/rule/Engine.hpp" #include "../version.h" #include "../Compositor.hpp" @@ -317,7 +318,7 @@ static std::string monitorsRequest(eHyprCtlOutputFormat format, std::string requ } static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) { - const auto tags = w->m_tags.getTags(); + const auto tags = w->m_ruleApplicator->m_tagKeeper.getTags(); if (format == eHyprCtlOutputFormat::FORMAT_JSON) return std::ranges::fold_left(tags, std::string(), @@ -1272,8 +1273,12 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) if (COMMAND.empty()) return "Invalid input: command is empty"; + g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = true; + std::string retval = g_pConfigManager->parseKeyword(COMMAND, VALUE); + g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = false; + // if we are executing a dynamic source we have to reload everything, so every if will have a check for source. if (COMMAND == "monitor" || COMMAND == "source") g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords @@ -1306,8 +1311,7 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) g_pConfigManager->updateWatcher(); // decorations will probably need a repaint - if (COMMAND.contains("decoration:") || COMMAND.contains("border") || COMMAND == "workspace" || COMMAND.contains("zoom_factor") || COMMAND == "source" || - COMMAND.starts_with("windowrule")) { + if (COMMAND.contains("decoration:") || COMMAND.contains("border") || COMMAND == "workspace" || COMMAND.contains("zoom_factor") || COMMAND == "source") { static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); for (auto const& m : g_pCompositor->m_monitors) { *(m->m_cursorZoom) = *PZOOMFACTOR; @@ -1316,6 +1320,9 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) } } + if (COMMAND.contains("windowrule ") || COMMAND.contains("windowrule[")) + g_pConfigManager->reloadRules(); + if (COMMAND.contains("workspace")) g_pConfigManager->ensurePersistentWorkspacesPresent(); @@ -1521,11 +1528,6 @@ static std::string dispatchSeterror(eHyprCtlOutputFormat format, std::string req return "ok"; } -static std::string dispatchSetProp(eHyprCtlOutputFormat format, std::string request) { - auto result = g_pKeybindManager->m_dispatchers["setprop"](request.substr(request.find_first_of(' ') + 1)); - return "DEPRECATED: use hyprctl dispatch setprop instead" + (result.success ? "" : "\n" + result.error); -} - static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string request) { CVarList vars(request, 0, ' '); @@ -1543,9 +1545,9 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ const bool FORMNORM = format == FORMAT_NORMAL; auto sizeToString = [&](bool max) -> std::string { - auto sizeValue = PWINDOW->m_windowData.minSize.valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE)); + auto sizeValue = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE)); if (max) - sizeValue = PWINDOW->m_windowData.maxSize.valueOr(Vector2D(INFINITY, INFINITY)); + sizeValue = PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D(INFINITY, INFINITY)); if (FORMNORM) return std::format("{} {}", sizeValue.x, sizeValue.y); @@ -1556,7 +1558,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ } }; - auto alphaToString = [&](CWindowOverridableVar& alpha, bool getAlpha) -> std::string { + auto alphaToString = [&](Desktop::Types::COverridableVar& alpha, bool getAlpha) -> std::string { if (FORMNORM) { if (getAlpha) return std::format("{}", alpha.valueOrDefault().alpha); @@ -1590,7 +1592,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ const auto* const ACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); - std::string borderColorString = PWINDOW->m_windowData.activeBorderColor.valueOr(*ACTIVECOLOR).toString(); + std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString(); if (FORMNORM) return borderColorString; else @@ -1603,7 +1605,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); - std::string borderColorString = PWINDOW->m_windowData.inactiveBorderColor.valueOr(*INACTIVECOLOR).toString(); + std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString(); if (FORMNORM) return borderColorString; else @@ -1618,38 +1620,92 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ return std::format(R"({{"{}": {}}})", PROP, prop.valueOrDefault()); }; - if (PROP == "animationstyle") { - auto& animationStyle = PWINDOW->m_windowData.animationStyle; + if (PROP == "animation") { + auto& animationStyle = PWINDOW->m_ruleApplicator->animationStyle(); if (FORMNORM) return animationStyle.valueOr("(unset)"); else return std::format(R"({{"{}": "{}"}})", PROP, animationStyle.valueOr("")); - } else if (PROP == "maxsize") + } else if (PROP == "max_size") return sizeToString(true); - else if (PROP == "minsize") + else if (PROP == "min_size") return sizeToString(false); - else if (PROP == "alpha") - return alphaToString(PWINDOW->m_windowData.alpha, true); - else if (PROP == "alphainactive") - return alphaToString(PWINDOW->m_windowData.alphaInactive, true); - else if (PROP == "alphafullscreen") - return alphaToString(PWINDOW->m_windowData.alphaFullscreen, true); - else if (PROP == "alphaoverride") - return alphaToString(PWINDOW->m_windowData.alpha, false); - else if (PROP == "alphainactiveoverride") - return alphaToString(PWINDOW->m_windowData.alphaInactive, false); - else if (PROP == "alphafullscreenoverride") - return alphaToString(PWINDOW->m_windowData.alphaFullscreen, false); - else if (PROP == "activebordercolor") + else if (PROP == "opacity") + return alphaToString(PWINDOW->m_ruleApplicator->alpha(), true); + else if (PROP == "opacity_inactive") + return alphaToString(PWINDOW->m_ruleApplicator->alphaInactive(), true); + else if (PROP == "opacity_fullscreen") + return alphaToString(PWINDOW->m_ruleApplicator->alphaFullscreen(), true); + else if (PROP == "opacity_override") + return alphaToString(PWINDOW->m_ruleApplicator->alpha(), false); + else if (PROP == "opacity_inactive_override") + return alphaToString(PWINDOW->m_ruleApplicator->alphaInactive(), false); + else if (PROP == "opacity_fullscreen_override") + return alphaToString(PWINDOW->m_ruleApplicator->alphaFullscreen(), false); + else if (PROP == "active_border_color") return borderColorToString(true); - else if (PROP == "inactivebordercolor") + else if (PROP == "inactive_border_color") return borderColorToString(false); - else if (auto search = NWindowProperties::boolWindowProperties.find(PROP); search != NWindowProperties::boolWindowProperties.end()) - return windowPropToString(*search->second(PWINDOW)); - else if (auto search = NWindowProperties::intWindowProperties.find(PROP); search != NWindowProperties::intWindowProperties.end()) - return windowPropToString(*search->second(PWINDOW)); - else if (auto search = NWindowProperties::floatWindowProperties.find(PROP); search != NWindowProperties::floatWindowProperties.end()) - return windowPropToString(*search->second(PWINDOW)); + else if (PROP == "allows_input") + return windowPropToString(PWINDOW->m_ruleApplicator->allowsInput()); + else if (PROP == "decorate") + return windowPropToString(PWINDOW->m_ruleApplicator->decorate()); + else if (PROP == "focus_on_activate") + return windowPropToString(PWINDOW->m_ruleApplicator->focusOnActivate()); + else if (PROP == "keep_aspect_ratio") + return windowPropToString(PWINDOW->m_ruleApplicator->keepAspectRatio()); + else if (PROP == "nearest_neighbor") + return windowPropToString(PWINDOW->m_ruleApplicator->nearestNeighbor()); + else if (PROP == "no_anim") + return windowPropToString(PWINDOW->m_ruleApplicator->noAnim()); + else if (PROP == "no_blur") + return windowPropToString(PWINDOW->m_ruleApplicator->noBlur()); + else if (PROP == "no_dim") + return windowPropToString(PWINDOW->m_ruleApplicator->noDim()); + else if (PROP == "no_focus") + return windowPropToString(PWINDOW->m_ruleApplicator->noFocus()); + else if (PROP == "no_max_size") + return windowPropToString(PWINDOW->m_ruleApplicator->noMaxSize()); + else if (PROP == "no_shadow") + return windowPropToString(PWINDOW->m_ruleApplicator->noShadow()); + else if (PROP == "no_shortcuts_inhibit") + return windowPropToString(PWINDOW->m_ruleApplicator->noShortcutsInhibit()); + else if (PROP == "opaque") + return windowPropToString(PWINDOW->m_ruleApplicator->opaque()); + else if (PROP == "dim_around") + return windowPropToString(PWINDOW->m_ruleApplicator->dimAround()); + else if (PROP == "force_rgbx") + return windowPropToString(PWINDOW->m_ruleApplicator->RGBX()); + else if (PROP == "sync_fullscreen") + return windowPropToString(PWINDOW->m_ruleApplicator->syncFullscreen()); + else if (PROP == "immediate") + return windowPropToString(PWINDOW->m_ruleApplicator->tearing()); + else if (PROP == "xray") + return windowPropToString(PWINDOW->m_ruleApplicator->xray()); + else if (PROP == "render_unfocused") + return windowPropToString(PWINDOW->m_ruleApplicator->renderUnfocused()); + else if (PROP == "no_follow_mouse") + return windowPropToString(PWINDOW->m_ruleApplicator->noFollowMouse()); + else if (PROP == "no_screen_share") + return windowPropToString(PWINDOW->m_ruleApplicator->noScreenShare()); + else if (PROP == "no_vrr") + return windowPropToString(PWINDOW->m_ruleApplicator->noVRR()); + else if (PROP == "persistent_size") + return windowPropToString(PWINDOW->m_ruleApplicator->persistentSize()); + else if (PROP == "stay_focused") + return windowPropToString(PWINDOW->m_ruleApplicator->stayFocused()); + else if (PROP == "idle_inhibit") + return windowPropToString(PWINDOW->m_ruleApplicator->idleInhibitMode()); + else if (PROP == "border_size") + return windowPropToString(PWINDOW->m_ruleApplicator->borderSize()); + else if (PROP == "rounding") + return windowPropToString(PWINDOW->m_ruleApplicator->rounding()); + else if (PROP == "rounding_power") + return windowPropToString(PWINDOW->m_ruleApplicator->roundingPower()); + else if (PROP == "scroll_mouse") + return windowPropToString(PWINDOW->m_ruleApplicator->scrollMouse()); + else if (PROP == "scroll_touchpad") + return windowPropToString(PWINDOW->m_ruleApplicator->scrollTouchpad()); return "prop not found"; } @@ -2014,7 +2070,6 @@ CHyprCtl::CHyprCtl() { registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin}); registerCommand(SHyprCtlCommand{"notify", false, dispatchNotify}); registerCommand(SHyprCtlCommand{"dismissnotify", false, dispatchDismissNotify}); - registerCommand(SHyprCtlCommand{"setprop", false, dispatchSetProp}); registerCommand(SHyprCtlCommand{"getprop", false, dispatchGetProp}); registerCommand(SHyprCtlCommand{"seterror", false, dispatchSeterror}); registerCommand(SHyprCtlCommand{"switchxkblayout", false, switchXKBLayoutRequest}); @@ -2130,8 +2185,7 @@ std::string CHyprCtl::getReply(std::string request) { if (!w->m_isMapped || !w->m_workspace || !w->m_workspace->isVisible()) continue; - w->updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(w); + Desktop::Rule::ruleEngine()->updateAllRules(); } for (auto const& m : g_pCompositor->m_monitors) { diff --git a/src/debug/HyprCtl.hpp b/src/debug/HyprCtl.hpp index 95bb65b8..d4f7aa14 100644 --- a/src/debug/HyprCtl.hpp +++ b/src/debug/HyprCtl.hpp @@ -25,9 +25,10 @@ class CHyprCtl { Hyprutils::OS::CFileDescriptor m_socketFD; struct { - bool all = false; - bool sysInfoConfig = false; - pid_t pid = 0; + bool all = false; + bool sysInfoConfig = false; + bool isDynamicKeyword = false; + pid_t pid = 0; SP> pendingPromise; } m_currentRequestParams; diff --git a/src/desktop/LayerRule.cpp b/src/desktop/LayerRule.cpp deleted file mode 100644 index d6bfcadf..00000000 --- a/src/desktop/LayerRule.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include "LayerRule.hpp" -#include -#include -#include "../debug/Log.hpp" - -static const auto RULES = std::unordered_set{"noanim", "blur", "blurpopups", "dimaround", "noscreenshare"}; -static const auto RULES_PREFIX = std::unordered_set{"ignorealpha", "ignorezero", "xray", "animation", "order", "abovelock"}; - -CLayerRule::CLayerRule(const std::string& rule_, const std::string& ns_) : m_targetNamespace(ns_), m_rule(rule_) { - const bool VALID = RULES.contains(m_rule) || std::ranges::any_of(RULES_PREFIX, [&rule_](const auto& prefix) { return rule_.starts_with(prefix); }); - - if (!VALID) - return; - - if (m_rule == "noanim") - m_ruleType = RULE_NOANIM; - else if (m_rule == "blur") - m_ruleType = RULE_BLUR; - else if (m_rule == "blurpopups") - m_ruleType = RULE_BLURPOPUPS; - else if (m_rule == "dimaround") - m_ruleType = RULE_DIMAROUND; - else if (m_rule == "noscreenshare") - m_ruleType = RULE_NOSCREENSHARE; - else if (m_rule.starts_with("ignorealpha")) - m_ruleType = RULE_IGNOREALPHA; - else if (m_rule.starts_with("ignorezero")) - m_ruleType = RULE_IGNOREZERO; - else if (m_rule.starts_with("xray")) - m_ruleType = RULE_XRAY; - else if (m_rule.starts_with("animation")) - m_ruleType = RULE_ANIMATION; - else if (m_rule.starts_with("order")) - m_ruleType = RULE_ORDER; - else if (m_rule.starts_with("abovelock")) - m_ruleType = RULE_ABOVELOCK; - else { - Debug::log(ERR, "CLayerRule: didn't match a rule that was found valid?!"); - m_ruleType = RULE_INVALID; - } -} diff --git a/src/desktop/LayerRule.hpp b/src/desktop/LayerRule.hpp deleted file mode 100644 index 7b6c8a6d..00000000 --- a/src/desktop/LayerRule.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include -#include "Rule.hpp" - -class CLayerRule { - public: - CLayerRule(const std::string& rule, const std::string& targetNS); - - enum eRuleType : uint8_t { - RULE_INVALID = 0, - RULE_NOANIM, - RULE_BLUR, - RULE_BLURPOPUPS, - RULE_DIMAROUND, - RULE_ABOVELOCK, - RULE_IGNOREALPHA, - RULE_IGNOREZERO, - RULE_XRAY, - RULE_ANIMATION, - RULE_ORDER, - RULE_ZUMBA, - RULE_NOSCREENSHARE - }; - - eRuleType m_ruleType = RULE_INVALID; - - const std::string m_targetNamespace; - const std::string m_rule; - - CRuleRegexContainer m_targetNamespaceRegex; -}; diff --git a/src/desktop/LayerSurface.cpp b/src/desktop/LayerSurface.cpp index 6278078d..aab0b15a 100644 --- a/src/desktop/LayerSurface.cpp +++ b/src/desktop/LayerSurface.cpp @@ -37,7 +37,7 @@ PHLLS CLayerSurface::create(SP resource) { pLS->m_monitor = pMonitor; pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS); - pLS->m_forceBlur = g_pConfigManager->shouldBlurLS(pLS->m_namespace); + pLS->m_ruleApplicator = makeUnique(pLS); g_pAnimationManager->createAnimation(0.f, pLS->m_alpha, g_pConfigManager->getAnimationPropertyConfig("fadeLayersIn"), pLS, AVARDAMAGE_ENTIRE); g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("layersIn"), pLS, AVARDAMAGE_ENTIRE); @@ -55,7 +55,7 @@ PHLLS CLayerSurface::create(SP resource) { void CLayerSurface::registerCallbacks() { m_alpha->setUpdateCallback([this](auto) { - if (m_dimAround && m_monitor) + if (m_ruleApplicator->dimAround().valueOrDefault() && m_monitor) g_pHyprRenderer->damageMonitor(m_monitor.lock()); }); } @@ -137,6 +137,8 @@ void CLayerSurface::onMap() { m_mapped = true; m_interactivity = m_layerSurface->m_current.interactivity; + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ALL); + m_layerSurface->m_surface->map(); // this layer might be re-mapped. @@ -149,8 +151,6 @@ void CLayerSurface::onMap() { if (!PMONITOR) return; - applyRules(); - PMONITOR->m_scheduledRecalc = true; g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id); @@ -398,83 +398,6 @@ void CLayerSurface::onCommit() { g_pCompositor->setPreferredTransformForSurface(m_surface->resource(), PMONITOR->m_transform); } -void CLayerSurface::applyRules() { - m_noAnimations = false; - m_forceBlur = false; - m_ignoreAlpha = false; - m_dimAround = false; - m_noScreenShare = false; - m_ignoreAlphaValue = 0.f; - m_xray = -1; - m_animationStyle.reset(); - - for (auto const& rule : g_pConfigManager->getMatchingRules(m_self.lock())) { - switch (rule->m_ruleType) { - case CLayerRule::RULE_NOANIM: { - m_noAnimations = true; - break; - } - case CLayerRule::RULE_BLUR: { - m_forceBlur = true; - break; - } - case CLayerRule::RULE_BLURPOPUPS: { - m_forceBlurPopups = true; - break; - } - case CLayerRule::RULE_IGNOREALPHA: - case CLayerRule::RULE_IGNOREZERO: { - const auto FIRST_SPACE_POS = rule->m_rule.find_first_of(' '); - std::string alphaValue = ""; - if (FIRST_SPACE_POS != std::string::npos) - alphaValue = rule->m_rule.substr(FIRST_SPACE_POS + 1); - - try { - m_ignoreAlpha = true; - if (!alphaValue.empty()) - m_ignoreAlphaValue = std::stof(alphaValue); - } catch (...) { Debug::log(ERR, "Invalid value passed to ignoreAlpha"); } - break; - } - case CLayerRule::RULE_DIMAROUND: { - m_dimAround = true; - break; - } - case CLayerRule::RULE_NOSCREENSHARE: { - m_noScreenShare = true; - break; - } - case CLayerRule::RULE_XRAY: { - CVarList vars{rule->m_rule, 0, ' '}; - m_xray = configStringToInt(vars[1]).value_or(false); - - break; - } - case CLayerRule::RULE_ANIMATION: { - CVarList vars{rule->m_rule, 2, 's'}; - m_animationStyle = vars[1]; - break; - } - case CLayerRule::RULE_ORDER: { - CVarList vars{rule->m_rule, 2, 's'}; - try { - m_order = std::stoi(vars[1]); - } catch (...) { Debug::log(ERR, "Invalid value passed to order"); } - break; - } - case CLayerRule::RULE_ABOVELOCK: { - m_aboveLockscreen = true; - - CVarList vars{rule->m_rule, 0, ' '}; - m_aboveLockscreenInteractable = configStringToInt(vars[1]).value_or(false); - - break; - } - default: break; - } - } -} - bool CLayerSurface::isFadedOut() { if (!m_fadingOut) return false; diff --git a/src/desktop/LayerSurface.hpp b/src/desktop/LayerSurface.hpp index b70739cd..5676e4d2 100644 --- a/src/desktop/LayerSurface.hpp +++ b/src/desktop/LayerSurface.hpp @@ -3,6 +3,7 @@ #include #include "../defines.hpp" #include "WLSurface.hpp" +#include "rule/layerRule/LayerRuleApplicator.hpp" #include "../helpers/AnimatedVariable.hpp" class CLayerShellResource; @@ -17,7 +18,6 @@ class CLayerSurface { public: ~CLayerSurface(); - void applyRules(); bool isFadedOut(); int popupsCount(); @@ -28,47 +28,35 @@ class CLayerSurface { WP m_layerSurface; // the header providing the enum type cannot be imported here - int m_interactivity = 0; + int m_interactivity = 0; - SP m_surface; + SP m_surface; - bool m_mapped = false; - uint32_t m_layer = 0; + bool m_mapped = false; + uint32_t m_layer = 0; - PHLMONITORREF m_monitor; + PHLMONITORREF m_monitor; - bool m_fadingOut = false; - bool m_readyToDelete = false; - bool m_noProcess = false; - bool m_noAnimations = false; + bool m_fadingOut = false; + bool m_readyToDelete = false; + bool m_noProcess = false; - bool m_forceBlur = false; - bool m_forceBlurPopups = false; - int64_t m_xray = -1; - bool m_ignoreAlpha = false; - float m_ignoreAlphaValue = 0.f; - bool m_dimAround = false; - bool m_noScreenShare = false; - int64_t m_order = 0; - bool m_aboveLockscreen = false; - bool m_aboveLockscreenInteractable = false; + UP m_ruleApplicator; - std::optional m_animationStyle; + PHLLSREF m_self; - PHLLSREF m_self; + CBox m_geometry = {0, 0, 0, 0}; + Vector2D m_position; + std::string m_namespace = ""; + UP m_popupHead; - CBox m_geometry = {0, 0, 0, 0}; - Vector2D m_position; - std::string m_namespace = ""; - UP m_popupHead; + pid_t getPID(); - pid_t getPID(); - - void onDestroy(); - void onMap(); - void onUnmap(); - void onCommit(); - MONITORID monitorID(); + void onDestroy(); + void onMap(); + void onUnmap(); + void onCommit(); + MONITORID monitorID(); private: struct { diff --git a/src/desktop/Rule.cpp b/src/desktop/Rule.cpp deleted file mode 100644 index 93f38de0..00000000 --- a/src/desktop/Rule.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include "../helpers/memory/Memory.hpp" -#include "Rule.hpp" -#include "../debug/Log.hpp" - -CRuleRegexContainer::CRuleRegexContainer(const std::string& regex_) { - const bool NEGATIVE = regex_.starts_with("negative:"); - - m_negative = NEGATIVE; - m_regex = makeUnique(NEGATIVE ? regex_.substr(9) : regex_); - - // TODO: maybe pop an error? - if (!m_regex->ok()) - Debug::log(ERR, "RuleRegexContainer: regex {} failed to parse!", regex_); -} - -bool CRuleRegexContainer::passes(const std::string& str) const { - if (!m_regex) - return false; - - return RE2::FullMatch(str, *m_regex) != m_negative; -} \ No newline at end of file diff --git a/src/desktop/Rule.hpp b/src/desktop/Rule.hpp deleted file mode 100644 index 9d3de70e..00000000 --- a/src/desktop/Rule.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -//NOLINTNEXTLINE -namespace re2 { - class RE2; -}; - -class CRuleRegexContainer { - public: - CRuleRegexContainer() = default; - - CRuleRegexContainer(const std::string& regex); - - bool passes(const std::string& str) const; - - private: - Hyprutils::Memory::CUniquePointer m_regex; - bool m_negative = false; -}; \ No newline at end of file diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp index 8671c003..b2712886 100644 --- a/src/desktop/Window.cpp +++ b/src/desktop/Window.cpp @@ -24,6 +24,7 @@ #include "../protocols/FractionalScale.hpp" #include "../xwayland/XWayland.hpp" #include "../helpers/Color.hpp" +#include "../helpers/math/Expression.hpp" #include "../events/Events.hpp" #include "../managers/XWaylandManager.hpp" #include "../render/Renderer.hpp" @@ -41,8 +42,9 @@ using enum NContentType::eContentType; PHLWINDOW CWindow::create(SP surface) { PHLWINDOW pWindow = SP(new CWindow(surface)); - pWindow->m_self = pWindow; - pWindow->m_isX11 = true; + pWindow->m_self = pWindow; + pWindow->m_isX11 = true; + pWindow->m_ruleApplicator = makeUnique(pWindow); g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realSize, g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); @@ -67,6 +69,7 @@ PHLWINDOW CWindow::create(SP resource) { pWindow->m_self = pWindow; resource->m_toplevel->m_window = pWindow; + pWindow->m_ruleApplicator = makeUnique(pWindow); g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realSize, g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); @@ -138,7 +141,7 @@ SBoxExtents CWindow::getFullWindowExtents() { const int BORDERSIZE = getRealBorderSize(); - if (m_windowData.dimAround.valueOrDefault()) { + if (m_ruleApplicator->dimAround().valueOrDefault()) { if (const auto PMONITOR = m_monitor.lock(); PMONITOR) return {.topLeft = {m_realPosition->value().x - PMONITOR->m_position.x, m_realPosition->value().y - PMONITOR->m_position.y}, .bottomRight = {PMONITOR->m_size.x - (m_realPosition->value().x - PMONITOR->m_position.x), @@ -191,7 +194,7 @@ SBoxExtents CWindow::getFullWindowExtents() { } CBox CWindow::getFullWindowBoundingBox() { - if (m_windowData.dimAround.valueOrDefault()) { + if (m_ruleApplicator->dimAround().valueOrDefault()) { if (const auto PMONITOR = m_monitor.lock(); PMONITOR) return {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; } @@ -251,7 +254,7 @@ SBoxExtents CWindow::getWindowExtentsUnified(uint64_t properties) { } CBox CWindow::getWindowBoxUnified(uint64_t properties) { - if (m_windowData.dimAround.valueOrDefault()) { + if (m_ruleApplicator->dimAround().valueOrDefault()) { const auto PMONITOR = m_monitor.lock(); if (PMONITOR) return {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; @@ -636,222 +639,6 @@ bool CWindow::isHidden() { return m_hidden; } -void CWindow::applyDynamicRule(const SP& r) { - const eOverridePriority priority = r->m_execRule ? PRIORITY_SET_PROP : PRIORITY_WINDOW_RULE; - static auto PCLAMP_TILED = CConfigValue("misc:size_limits_tiled"); - - switch (r->m_ruleType) { - case CWindowRule::RULE_TAG: { - CVarList vars{r->m_rule, 0, 's', true}; - - if (vars.size() == 2 && vars[0] == "tag") - m_tags.applyTag(vars[1], true); - else - Debug::log(ERR, "Tag rule invalid: {}", r->m_rule); - break; - } - case CWindowRule::RULE_OPACITY: { - try { - CVarList vars(r->m_rule, 0, ' '); - - int opacityIDX = 0; - - for (auto const& r : vars) { - if (r == "opacity") - continue; - - if (r == "override") { - if (opacityIDX == 1) - m_windowData.alpha = CWindowOverridableVar(SAlphaValue{.alpha = m_windowData.alpha.value().alpha, .overridden = true}, priority); - else if (opacityIDX == 2) - m_windowData.alphaInactive = CWindowOverridableVar(SAlphaValue{.alpha = m_windowData.alphaInactive.value().alpha, .overridden = true}, priority); - else if (opacityIDX == 3) - m_windowData.alphaFullscreen = CWindowOverridableVar(SAlphaValue{.alpha = m_windowData.alphaFullscreen.value().alpha, .overridden = true}, priority); - } else { - if (opacityIDX == 0) { - m_windowData.alpha = CWindowOverridableVar(SAlphaValue{.alpha = std::stof(r), .overridden = false}, priority); - } else if (opacityIDX == 1) { - m_windowData.alphaInactive = CWindowOverridableVar(SAlphaValue{.alpha = std::stof(r), .overridden = false}, priority); - } else if (opacityIDX == 2) { - m_windowData.alphaFullscreen = CWindowOverridableVar(SAlphaValue{.alpha = std::stof(r), .overridden = false}, priority); - } else { - throw std::runtime_error("more than 3 alpha values"); - } - - opacityIDX++; - } - } - - if (opacityIDX == 1) { - m_windowData.alphaInactive = m_windowData.alpha; - m_windowData.alphaFullscreen = m_windowData.alpha; - } - } catch (std::exception& e) { Debug::log(ERR, "Opacity rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_ANIMATION: { - auto STYLE = r->m_rule.substr(r->m_rule.find_first_of(' ') + 1); - m_windowData.animationStyle = CWindowOverridableVar(STYLE, priority); - break; - } - case CWindowRule::RULE_BORDERCOLOR: { - 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(r->m_rule.substr(r->m_rule.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_windowData.activeBorderColor = CWindowOverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[0]).value_or(0))), priority); - m_windowData.inactiveBorderColor = CWindowOverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), priority); - return; - } - - 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", r->m_rule); - else if (activeBorderGradient.m_colors.empty()) - Debug::log(WARN, "Bordercolor rule \"{}\" has no colors, ignoring", r->m_rule); - else if (inactiveBorderGradient.m_colors.empty()) - m_windowData.activeBorderColor = CWindowOverridableVar(activeBorderGradient, priority); - else { - m_windowData.activeBorderColor = CWindowOverridableVar(activeBorderGradient, priority); - m_windowData.inactiveBorderColor = CWindowOverridableVar(inactiveBorderGradient, priority); - } - } catch (std::exception& e) { Debug::log(ERR, "BorderColor rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_IDLEINHIBIT: { - auto IDLERULE = r->m_rule.substr(r->m_rule.find_first_of(' ') + 1); - - if (IDLERULE == "none") - m_idleInhibitMode = IDLEINHIBIT_NONE; - else if (IDLERULE == "always") - m_idleInhibitMode = IDLEINHIBIT_ALWAYS; - else if (IDLERULE == "focus") - m_idleInhibitMode = IDLEINHIBIT_FOCUS; - else if (IDLERULE == "fullscreen") - m_idleInhibitMode = IDLEINHIBIT_FULLSCREEN; - else - Debug::log(ERR, "Rule idleinhibit: unknown mode {}", IDLERULE); - break; - } - case CWindowRule::RULE_MAXSIZE: { - try { - if (!m_isFloating && !sc(*PCLAMP_TILED)) - return; - const auto VEC = configStringToVector2D(r->m_rule.substr(8)); - if (VEC.x < 1 || VEC.y < 1) { - Debug::log(ERR, "Invalid size for maxsize"); - return; - } - - m_windowData.maxSize = CWindowOverridableVar(VEC, priority); - clampWindowSize(std::nullopt, m_windowData.maxSize.value()); - - } catch (std::exception& e) { Debug::log(ERR, "maxsize rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_MINSIZE: { - try { - if (!m_isFloating && !sc(*PCLAMP_TILED)) - return; - const auto VEC = configStringToVector2D(r->m_rule.substr(8)); - if (VEC.x < 1 || VEC.y < 1) { - Debug::log(ERR, "Invalid size for minsize"); - return; - } - - m_windowData.minSize = CWindowOverridableVar(VEC, priority); - clampWindowSize(m_windowData.minSize.value(), std::nullopt); - - if (m_groupData.pNextWindow.expired()) - setHidden(false); - } catch (std::exception& e) { Debug::log(ERR, "minsize rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_RENDERUNFOCUSED: { - m_windowData.renderUnfocused = CWindowOverridableVar(true, priority); - g_pHyprRenderer->addWindowToRenderUnfocused(m_self.lock()); - break; - } - case CWindowRule::RULE_PROP: { - const CVarList VARS(r->m_rule, 0, ' '); - if (auto search = NWindowProperties::intWindowProperties.find(VARS[1]); search != NWindowProperties::intWindowProperties.end()) { - try { - *(search->second(m_self.lock())) = CWindowOverridableVar(sc(std::stoi(VARS[2])), priority); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - } else if (auto search = NWindowProperties::floatWindowProperties.find(VARS[1]); search != NWindowProperties::floatWindowProperties.end()) { - try { - *(search->second(m_self.lock())) = CWindowOverridableVar(std::stof(VARS[2]), priority); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - } else if (auto search = NWindowProperties::boolWindowProperties.find(VARS[1]); search != NWindowProperties::boolWindowProperties.end()) { - try { - *(search->second(m_self.lock())) = CWindowOverridableVar(VARS[2].empty() ? true : sc(std::stoi(VARS[2])), priority); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - } - break; - } - case CWindowRule::RULE_PERSISTENTSIZE: { - m_windowData.persistentSize = CWindowOverridableVar(true, PRIORITY_WINDOW_RULE); - break; - } - case CWindowRule::RULE_NOVRR: { - m_windowData.noVRR = CWindowOverridableVar(true, priority); - break; - } - default: break; - } -} - -void CWindow::updateDynamicRules() { - m_windowData.alpha.unset(PRIORITY_WINDOW_RULE); - m_windowData.alphaInactive.unset(PRIORITY_WINDOW_RULE); - m_windowData.alphaFullscreen.unset(PRIORITY_WINDOW_RULE); - - unsetWindowData(PRIORITY_WINDOW_RULE); - - m_windowData.animationStyle.unset(PRIORITY_WINDOW_RULE); - m_windowData.maxSize.unset(PRIORITY_WINDOW_RULE); - m_windowData.minSize.unset(PRIORITY_WINDOW_RULE); - - m_windowData.activeBorderColor.unset(PRIORITY_WINDOW_RULE); - m_windowData.inactiveBorderColor.unset(PRIORITY_WINDOW_RULE); - - m_windowData.renderUnfocused.unset(PRIORITY_WINDOW_RULE); - m_windowData.noVRR.unset(PRIORITY_WINDOW_RULE); - - m_idleInhibitMode = IDLEINHIBIT_NONE; - - m_tags.removeDynamicTags(); - - m_matchedRules = g_pConfigManager->getMatchingRules(m_self.lock()); - for (const auto& r : m_matchedRules) { - applyDynamicRule(r); - } - - EMIT_HOOK_EVENT("windowUpdateRules", m_self.lock()); - - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); -} - // check if the point is "hidden" under a rounded corner of the window // it is assumed that the point is within the real window box (m_vRealPosition, m_vRealSize) // otherwise behaviour is undefined @@ -924,6 +711,8 @@ void CWindow::createGroup() { g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("1,{:x}", rc(this))}); } + + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); } void CWindow::destroyGroup() { @@ -943,6 +732,7 @@ void CWindow::destroyGroup() { g_pCompositor->updateAllWindowsAnimatedDecorationValues(); g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{:x}", rc(this))}); + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); return; } @@ -969,6 +759,7 @@ void CWindow::destroyGroup() { g_pKeybindManager->m_groupsLocked = true; for (auto const& w : members) { g_pLayoutManager->getCurrentLayout()->onWindowCreated(w); + w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); w->updateWindowDecos(); } g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; @@ -982,6 +773,8 @@ void CWindow::destroyGroup() { if (!addresses.empty()) addresses.pop_back(); + + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{}", addresses)}); } @@ -1217,16 +1010,16 @@ float CWindow::rounding() { static auto PROUNDING = CConfigValue("decoration:rounding"); static auto PROUNDINGPOWER = CConfigValue("decoration:rounding_power"); - float roundingPower = m_windowData.roundingPower.valueOr(*PROUNDINGPOWER); - float rounding = m_windowData.rounding.valueOr(*PROUNDING) * (roundingPower / 2.0); /* Make perceived roundness consistent. */ + float roundingPower = m_ruleApplicator->roundingPower().valueOr(*PROUNDINGPOWER); + float rounding = m_ruleApplicator->rounding().valueOr(*PROUNDING) * (roundingPower / 2.0); /* Make perceived roundness consistent. */ - return m_windowData.noRounding.valueOrDefault() ? 0 : rounding; + return rounding; } float CWindow::roundingPower() { static auto PROUNDINGPOWER = CConfigValue("decoration:rounding_power"); - return m_windowData.roundingPower.valueOr(std::clamp(*PROUNDINGPOWER, 1.F, 10.F)); + return m_ruleApplicator->roundingPower().valueOr(std::clamp(*PROUNDINGPOWER, 1.F, 10.F)); } void CWindow::updateWindowData() { @@ -1236,50 +1029,43 @@ void CWindow::updateWindowData() { } void CWindow::updateWindowData(const SWorkspaceRule& workspaceRule) { - static auto PNOBORDERONFLOATING = CConfigValue("general:no_border_on_floating"); - - if (*PNOBORDERONFLOATING) - m_windowData.noBorder = CWindowOverridableVar(m_isFloating, PRIORITY_LAYOUT); - else - m_windowData.noBorder.unset(PRIORITY_LAYOUT); - - m_windowData.borderSize.matchOptional(workspaceRule.borderSize, PRIORITY_WORKSPACE_RULE); - m_windowData.decorate.matchOptional(workspaceRule.decorate, PRIORITY_WORKSPACE_RULE); - m_windowData.noBorder.matchOptional(workspaceRule.noBorder, PRIORITY_WORKSPACE_RULE); - m_windowData.noRounding.matchOptional(workspaceRule.noRounding, PRIORITY_WORKSPACE_RULE); - m_windowData.noShadow.matchOptional(workspaceRule.noShadow, PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->borderSize().matchOptional(workspaceRule.borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->decorate().matchOptional(workspaceRule.decorate, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->borderSize().matchOptional(workspaceRule.noBorder ? std::optional(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->rounding().matchOptional(workspaceRule.noRounding.value_or(false) ? std::optional(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->noShadow().matchOptional(workspaceRule.noShadow, Desktop::Types::PRIORITY_WORKSPACE_RULE); } int CWindow::getRealBorderSize() { - if (m_windowData.noBorder.valueOrDefault() || (m_workspace && isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) || !m_windowData.decorate.valueOrDefault()) + if ((m_workspace && isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) || !m_ruleApplicator->decorate().valueOrDefault()) return 0; static auto PBORDERSIZE = CConfigValue("general:border_size"); - return m_windowData.borderSize.valueOr(*PBORDERSIZE); + return m_ruleApplicator->borderSize().valueOr(*PBORDERSIZE); } float CWindow::getScrollMouse() { static auto PINPUTSCROLLFACTOR = CConfigValue("input:scroll_factor"); - return m_windowData.scrollMouse.valueOr(*PINPUTSCROLLFACTOR); + return m_ruleApplicator->scrollMouse().valueOr(*PINPUTSCROLLFACTOR); } float CWindow::getScrollTouchpad() { static auto PTOUCHPADSCROLLFACTOR = CConfigValue("input:touchpad:scroll_factor"); - return m_windowData.scrollTouchpad.valueOr(*PTOUCHPADSCROLLFACTOR); + return m_ruleApplicator->scrollTouchpad().valueOr(*PTOUCHPADSCROLLFACTOR); } bool CWindow::isScrollMouseOverridden() { - return m_windowData.scrollMouse.hasValue(); + return m_ruleApplicator->scrollMouse().hasValue(); } bool CWindow::isScrollTouchpadOverridden() { - return m_windowData.scrollTouchpad.hasValue(); + return m_ruleApplicator->scrollTouchpad().hasValue(); } bool CWindow::canBeTorn() { static auto PTEARING = CConfigValue("general:allow_tearing"); - return m_windowData.tearing.valueOr(m_tearingHint) && *PTEARING; + return m_ruleApplicator->tearing().valueOr(m_tearingHint) && *PTEARING; } void CWindow::setSuspended(bool suspend) { @@ -1454,7 +1240,8 @@ void CWindow::activate(bool force) { g_pEventManager->postEvent(SHyprIPCEvent{.event = "urgent", .data = std::format("{:x}", rc(this))}); EMIT_HOOK_EVENT("urgent", m_self.lock()); - if (!force && (!m_windowData.focusOnActivate.valueOr(*PFOCUSONACTIVATE) || (m_suppressedEvents & SUPPRESS_ACTIVATE_FOCUSONLY) || (m_suppressedEvents & SUPPRESS_ACTIVATE))) + if (!force && + (!m_ruleApplicator->focusOnActivate().valueOr(*PFOCUSONACTIVATE) || (m_suppressedEvents & SUPPRESS_ACTIVATE_FOCUSONLY) || (m_suppressedEvents & SUPPRESS_ACTIVATE))) return; if (!m_isMapped) { @@ -1539,8 +1326,7 @@ void CWindow::onUpdateMeta() { } if (doUpdate) { - updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(m_self.lock()); + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_TITLE | Desktop::Rule::RULE_PROP_CLASS); updateToplevel(); } } @@ -1718,18 +1504,6 @@ PHLWINDOW CWindow::getSwallower() { return candidates[0]; } -void CWindow::unsetWindowData(eOverridePriority priority) { - for (auto const& element : NWindowProperties::boolWindowProperties) { - element.second(m_self.lock())->unset(priority); - } - for (auto const& element : NWindowProperties::intWindowProperties) { - element.second(m_self.lock())->unset(priority); - } - for (auto const& element : NWindowProperties::floatWindowProperties) { - element.second(m_self.lock())->unset(priority); - } -} - bool CWindow::isX11OverrideRedirect() { return m_xwaylandSurface && m_xwaylandSurface->m_overrideRedirect; } @@ -1753,7 +1527,7 @@ Vector2D CWindow::requestedMinSize() { Vector2D CWindow::requestedMaxSize() { constexpr int NO_MAX_SIZE_LIMIT = 99999; - if (((m_isX11 && !m_xwaylandSurface->m_sizeHints) || (!m_isX11 && (!m_xdgSurface || !m_xdgSurface->m_toplevel)) || m_windowData.noMaxSize.valueOrDefault())) + if (((m_isX11 && !m_xwaylandSurface->m_sizeHints) || (!m_isX11 && (!m_xdgSurface || !m_xdgSurface->m_toplevel)) || m_ruleApplicator->noMaxSize().valueOrDefault())) return Vector2D(NO_MAX_SIZE_LIMIT, NO_MAX_SIZE_LIMIT); Vector2D maxSize = m_isX11 ? Vector2D(m_xwaylandSurface->m_sizeHints->max_width, m_xwaylandSurface->m_sizeHints->max_height) : m_xdgSurface->m_toplevel->layoutMaxSize(); @@ -1951,3 +1725,127 @@ Vector2D CWindow::getReportedSize() { return m_wlSurface->resource()->m_current.ackedSize; return m_reportedSize; } + +void CWindow::updateDecorationValues() { + static auto PACTIVECOL = CConfigValue("general:col.active_border"); + static auto PINACTIVECOL = CConfigValue("general:col.inactive_border"); + static auto PNOGROUPACTIVECOL = CConfigValue("general:col.nogroup_border_active"); + static auto PNOGROUPINACTIVECOL = CConfigValue("general:col.nogroup_border"); + static auto PGROUPACTIVECOL = CConfigValue("group:col.border_active"); + static auto PGROUPINACTIVECOL = CConfigValue("group:col.border_inactive"); + static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); + static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); + static auto PINACTIVEALPHA = CConfigValue("decoration:inactive_opacity"); + static auto PACTIVEALPHA = CConfigValue("decoration:active_opacity"); + static auto PFULLSCREENALPHA = CConfigValue("decoration:fullscreen_opacity"); + static auto PSHADOWCOL = CConfigValue("decoration:shadow:color"); + static auto PSHADOWCOLINACTIVE = CConfigValue("decoration:shadow:color_inactive"); + static auto PDIMSTRENGTH = CConfigValue("decoration:dim_strength"); + static auto PDIMENABLED = CConfigValue("decoration:dim_inactive"); + static auto PDIMMODAL = CConfigValue("decoration:dim_modal"); + + auto* const ACTIVECOL = sc((PACTIVECOL.ptr())->getData()); + auto* const INACTIVECOL = sc((PINACTIVECOL.ptr())->getData()); + auto* const NOGROUPACTIVECOL = sc((PNOGROUPACTIVECOL.ptr())->getData()); + auto* const NOGROUPINACTIVECOL = sc((PNOGROUPINACTIVECOL.ptr())->getData()); + auto* const GROUPACTIVECOL = sc((PGROUPACTIVECOL.ptr())->getData()); + auto* const GROUPINACTIVECOL = sc((PGROUPINACTIVECOL.ptr())->getData()); + auto* const GROUPACTIVELOCKEDCOL = sc((PGROUPACTIVELOCKEDCOL.ptr())->getData()); + auto* const GROUPINACTIVELOCKEDCOL = sc((PGROUPINACTIVELOCKEDCOL.ptr())->getData()); + + auto setBorderColor = [&](CGradientValueData grad) -> void { + if (grad == m_realBorderColor) + return; + + m_realBorderColorPrevious = m_realBorderColor; + m_realBorderColor = grad; + m_borderFadeAnimationProgress->setValueAndWarp(0.f); + *m_borderFadeAnimationProgress = 1.f; + }; + + const bool IS_SHADOWED_BY_MODAL = m_xdgSurface && m_xdgSurface->m_toplevel && m_xdgSurface->m_toplevel->anyChildModal(); + + // border + const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(m_self.lock()); + if (RENDERDATA.isBorderGradient) + setBorderColor(*RENDERDATA.borderGradient); + else { + const bool GROUPLOCKED = m_groupData.pNextWindow.lock() ? getGroupHead()->m_groupData.locked : false; + if (m_self == g_pCompositor->m_lastWindow) { + const auto* const ACTIVECOLOR = + !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); + setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR)); + } else { + const auto* const INACTIVECOLOR = + !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR)); + } + } + + // opacity + const auto PWORKSPACE = m_workspace; + if (isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) { + *m_activeInactiveAlpha = m_ruleApplicator->alphaFullscreen().valueOrDefault().applyAlpha(*PFULLSCREENALPHA); + } else { + if (m_self == g_pCompositor->m_lastWindow) + *m_activeInactiveAlpha = m_ruleApplicator->alpha().valueOrDefault().applyAlpha(*PACTIVEALPHA); + else + *m_activeInactiveAlpha = m_ruleApplicator->alphaInactive().valueOrDefault().applyAlpha(*PINACTIVEALPHA); + } + + // dim + float goalDim = 1.F; + if (m_self == g_pCompositor->m_lastWindow.lock() || m_ruleApplicator->noDim().valueOrDefault() || !*PDIMENABLED) + goalDim = 0; + else + goalDim = *PDIMSTRENGTH; + + if (IS_SHADOWED_BY_MODAL && *PDIMMODAL) + goalDim += (1.F - goalDim) / 2.F; + + *m_dimPercent = goalDim; + + // shadow + if (!isX11OverrideRedirect() && !m_X11DoesntWantBorders) { + if (m_self == g_pCompositor->m_lastWindow) + *m_realShadowColor = CHyprColor(*PSHADOWCOL); + else + *m_realShadowColor = CHyprColor(*PSHADOWCOLINACTIVE != -1 ? *PSHADOWCOLINACTIVE : *PSHADOWCOL); + } else + m_realShadowColor->setValueAndWarp(CHyprColor(0, 0, 0, 0)); // no shadow + + updateWindowDecos(); +} + +std::optional CWindow::calculateSingleExpr(const std::string& s) { + const auto PMONITOR = m_monitor ? m_monitor : g_pCompositor->m_lastMonitor; + const auto CURSOR_LOCAL = g_pInputManager->getMouseCoordsInternal() - (PMONITOR ? PMONITOR->m_position : Vector2D{}); + + Math::CExpression expr; + expr.addVariable("window_w", m_realSize->goal().x); + expr.addVariable("window_h", m_realSize->goal().y); + expr.addVariable("window_x", m_realPosition->goal().x - (PMONITOR ? PMONITOR->m_position.x : 0)); + expr.addVariable("window_y", m_realPosition->goal().y - (PMONITOR ? PMONITOR->m_position.y : 0)); + + expr.addVariable("monitor_w", PMONITOR ? PMONITOR->m_size.x : 1920); + expr.addVariable("monitor_h", PMONITOR ? PMONITOR->m_size.y : 1080); + + expr.addVariable("cursor_x", CURSOR_LOCAL.x); + expr.addVariable("cursor_y", CURSOR_LOCAL.y); + + return expr.compute(s); +} + +std::optional CWindow::calculateExpression(const std::string& s) { + auto spacePos = s.find(' '); + if (spacePos == std::string::npos) + return std::nullopt; + + const auto LHS = calculateSingleExpr(s.substr(0, spacePos)); + const auto RHS = calculateSingleExpr(s.substr(spacePos + 1)); + + if (!LHS || !RHS) + return std::nullopt; + + return Vector2D{*LHS, *RHS}; +} diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp index 0a7e207c..63492682 100644 --- a/src/desktop/Window.hpp +++ b/src/desktop/Window.hpp @@ -16,20 +16,12 @@ #include "Subsurface.hpp" #include "WLSurface.hpp" #include "Workspace.hpp" -#include "WindowRule.hpp" -#include "WindowOverridableVar.hpp" +#include "rule/windowRule/WindowRuleApplicator.hpp" #include "../protocols/types/ContentType.hpp" class CXDGSurfaceResource; class CXWaylandSurface; -enum eIdleInhibitMode : uint8_t { - IDLEINHIBIT_NONE = 0, - IDLEINHIBIT_ALWAYS, - IDLEINHIBIT_FULLSCREEN, - IDLEINHIBIT_FOCUS -}; - enum eGroupRules : uint8_t { // effective only during first map, except for _ALWAYS variant GROUP_NONE = 0, @@ -65,65 +57,6 @@ enum eSuppressEvents : uint8_t { class IWindowTransformer; -struct SAlphaValue { - float alpha; - bool overridden; - - float applyAlpha(float a) const { - if (overridden) - return alpha; - else - return alpha * a; - }; -}; - -struct SWindowData { - CWindowOverridableVar alpha = SAlphaValue{.alpha = 1.f, .overridden = false}; - CWindowOverridableVar alphaInactive = SAlphaValue{.alpha = 1.f, .overridden = false}; - CWindowOverridableVar alphaFullscreen = SAlphaValue{.alpha = 1.f, .overridden = false}; - - CWindowOverridableVar allowsInput = false; - CWindowOverridableVar dimAround = false; - CWindowOverridableVar decorate = true; - CWindowOverridableVar focusOnActivate = false; - CWindowOverridableVar keepAspectRatio = false; - CWindowOverridableVar nearestNeighbor = false; - CWindowOverridableVar noAnim = false; - CWindowOverridableVar noBorder = false; - CWindowOverridableVar noBlur = false; - CWindowOverridableVar noDim = false; - CWindowOverridableVar noFocus = false; - CWindowOverridableVar noMaxSize = false; - CWindowOverridableVar noRounding = false; - CWindowOverridableVar noShadow = false; - CWindowOverridableVar noShortcutsInhibit = false; - CWindowOverridableVar opaque = false; - CWindowOverridableVar RGBX = false; - CWindowOverridableVar syncFullscreen = true; - CWindowOverridableVar tearing = false; - CWindowOverridableVar xray = false; - CWindowOverridableVar renderUnfocused = false; - CWindowOverridableVar noFollowMouse = false; - CWindowOverridableVar noScreenShare = false; - CWindowOverridableVar noVRR = false; - - CWindowOverridableVar borderSize = {std::string("general:border_size"), sc(0), std::nullopt}; - CWindowOverridableVar rounding = {std::string("decoration:rounding"), sc(0), std::nullopt}; - - CWindowOverridableVar roundingPower = {std::string("decoration:rounding_power")}; - CWindowOverridableVar scrollMouse = {std::string("input:scroll_factor")}; - CWindowOverridableVar scrollTouchpad = {std::string("input:touchpad:scroll_factor")}; - - CWindowOverridableVar animationStyle; - CWindowOverridableVar maxSize; - CWindowOverridableVar minSize; - - CWindowOverridableVar activeBorderColor; - CWindowOverridableVar inactiveBorderColor; - - CWindowOverridableVar persistentSize; -}; - struct SInitialWorkspaceToken { PHLWINDOWREF primaryOwner; std::string workspace; @@ -256,7 +189,7 @@ class CWindow { std::vector m_decosToRemove; // Special render data, rules, etc - SWindowData m_windowData; + UP m_ruleApplicator; // Transformers std::vector> m_transformers; @@ -280,15 +213,9 @@ class CWindow { bool m_currentlySwallowed = false; bool m_groupSwallowed = false; - // focus stuff - bool m_stayFocused = false; - // for toplevel monitor events MONITORID m_lastSurfaceMonitorID = -1; - // for idle inhibiting windows - eIdleInhibitMode m_idleInhibitMode = IDLEINHIBIT_NONE; - // initial token. Will be unregistered on workspace change or timeout of 2 minutes std::string m_initialWorkspaceToken = ""; @@ -303,12 +230,6 @@ class CWindow { bool m_tearingHint = false; - // stores the currently matched window rules - std::vector> m_matchedRules; - - // window tags - CTagKeeper m_tags; - // ANR PHLANIMVAR m_notRespondingTint; @@ -342,8 +263,7 @@ class CWindow { void onMap(); void setHidden(bool hidden); bool isHidden(); - void applyDynamicRule(const SP& r); - void updateDynamicRules(); + void updateDecorationValues(); SBoxExtents getFullWindowReservedArea(); Vector2D middle(); bool opaque(); @@ -397,7 +317,6 @@ class CWindow { std::string fetchClass(); void warpCursor(bool force = false); PHLWINDOW getSwallower(); - void unsetWindowData(eOverridePriority priority); bool isX11OverrideRedirect(); bool isModal(); Vector2D requestedMinSize(); @@ -418,6 +337,7 @@ class CWindow { bool priorityFocus(); SP getSolitaryResource(); Vector2D getReportedSize(); + std::optional calculateExpression(const std::string& s); CBox getWindowMainSurfaceBox() const { return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y}; @@ -448,6 +368,8 @@ class CWindow { } m_listeners; private: + std::optional calculateSingleExpr(const std::string& s); + // For hidden windows and stuff bool m_hidden = false; bool m_suspended = false; @@ -474,45 +396,6 @@ inline bool validMapped(PHLWINDOWREF w) { return w->m_isMapped; } -namespace NWindowProperties { - static const std::unordered_map*(const PHLWINDOW&)>> boolWindowProperties = { - {"allowsinput", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.allowsInput; }}, - {"dimaround", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.dimAround; }}, - {"decorate", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.decorate; }}, - {"focusonactivate", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.focusOnActivate; }}, - {"keepaspectratio", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.keepAspectRatio; }}, - {"nearestneighbor", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.nearestNeighbor; }}, - {"noanim", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noAnim; }}, - {"noblur", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noBlur; }}, - {"noborder", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noBorder; }}, - {"nodim", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noDim; }}, - {"nofocus", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noFocus; }}, - {"nomaxsize", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noMaxSize; }}, - {"norounding", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noRounding; }}, - {"noshadow", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noShadow; }}, - {"noshortcutsinhibit", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noShortcutsInhibit; }}, - {"opaque", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.opaque; }}, - {"forcergbx", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.RGBX; }}, - {"syncfullscreen", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.syncFullscreen; }}, - {"novrr", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noVRR; }}, - {"immediate", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.tearing; }}, - {"xray", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.xray; }}, - {"nofollowmouse", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noFollowMouse; }}, - {"noscreenshare", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noScreenShare; }}, - }; - - const std::unordered_map*(const PHLWINDOW&)>> intWindowProperties = { - {"rounding", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.rounding; }}, - {"bordersize", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.borderSize; }}, - }; - - const std::unordered_map*(PHLWINDOW)>> floatWindowProperties = { - {"roundingpower", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.roundingPower; }}, - {"scrollmouse", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.scrollMouse; }}, - {"scrolltouchpad", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.scrollTouchpad; }}, - }; -}; - /** format specification - 'x', only address, equivalent of (uintpr_t)CWindow* diff --git a/src/desktop/WindowOverridableVar.hpp b/src/desktop/WindowOverridableVar.hpp deleted file mode 100644 index ea113d3e..00000000 --- a/src/desktop/WindowOverridableVar.hpp +++ /dev/null @@ -1,132 +0,0 @@ -#pragma once - -#include -#include -#include -#include "../config/ConfigValue.hpp" - -enum eOverridePriority : uint8_t { - PRIORITY_LAYOUT = 0, - PRIORITY_WORKSPACE_RULE, - PRIORITY_WINDOW_RULE, - PRIORITY_SET_PROP, -}; - -template -T clampOptional(T const& value, std::optional const& min, std::optional const& max) { - return std::clamp(value, min.value_or(std::numeric_limits::min()), max.value_or(std::numeric_limits::max())); -} - -template || std::is_same_v || std::is_same_v> -class CWindowOverridableVar { - public: - CWindowOverridableVar(T const& value, eOverridePriority priority) { - m_values[priority] = value; - } - - CWindowOverridableVar(T const& value) : m_defaultValue{value} {} - CWindowOverridableVar(T const& value, std::optional const& min, std::optional const& max = std::nullopt) : m_defaultValue{value}, m_minValue{min}, m_maxValue{max} {} - CWindowOverridableVar(std::string const& value) - requires(Extended && !std::is_same_v) - : m_configValue(SP>(new CConfigValue(value))) {} - CWindowOverridableVar(std::string const& value, std::optional const& min, std::optional const& max = std::nullopt) - requires(Extended && !std::is_same_v) - : m_minValue(min), m_maxValue(max), m_configValue(SP>(new CConfigValue(value))) {} - - CWindowOverridableVar() = default; - ~CWindowOverridableVar() = default; - - CWindowOverridableVar& operator=(CWindowOverridableVar const& other) { - // Self-assignment check - if (this == &other) - return *this; - - for (auto const& value : other.m_values) { - if constexpr (Extended && !std::is_same_v) - m_values[value.first] = clampOptional(value.second, m_minValue, m_maxValue); - else - m_values[value.first] = value.second; - } - - return *this; - } - - void unset(eOverridePriority priority) { - m_values.erase(priority); - } - - bool hasValue() { - return !m_values.empty(); - } - - T value() { - if (!m_values.empty()) - return std::prev(m_values.end())->second; - else - throw std::bad_optional_access(); - } - - T valueOr(T const& other) { - if (hasValue()) - return value(); - else - return other; - } - - T valueOrDefault() - requires(Extended && !std::is_same_v) - { - if (hasValue()) - return value(); - else if (m_defaultValue.has_value()) - return m_defaultValue.value(); - else - return **std::any_cast>>(m_configValue); - } - - T valueOrDefault() - requires(!Extended || std::is_same_v) - { - if (hasValue()) - return value(); - else if (!m_defaultValue.has_value()) - throw std::bad_optional_access(); - else - return m_defaultValue.value(); - } - - eOverridePriority getPriority() { - if (!m_values.empty()) - return std::prev(m_values.end())->first; - else - throw std::bad_optional_access(); - } - - void increment(T const& other, eOverridePriority priority) { - if constexpr (std::is_same_v) - m_values[priority] = valueOr(false) ^ other; - else - m_values[priority] = clampOptional(valueOrDefault() + other, m_minValue, m_maxValue); - } - - void matchOptional(std::optional const& optValue, eOverridePriority priority) { - if (optValue.has_value()) - m_values[priority] = optValue.value(); - else - unset(priority); - } - - operator std::optional() { - if (hasValue()) - return value(); - else - return std::nullopt; - } - - private: - std::map m_values; - std::optional m_defaultValue; // used for toggling, so required for bool - std::optional m_minValue; - std::optional m_maxValue; - std::any m_configValue; // only there for select variables -}; diff --git a/src/desktop/WindowRule.cpp b/src/desktop/WindowRule.cpp deleted file mode 100644 index dc6564ca..00000000 --- a/src/desktop/WindowRule.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "WindowRule.hpp" -#include -#include -#include -#include "../config/ConfigManager.hpp" - -static const auto RULES = std::unordered_set{ - "float", "fullscreen", "maximize", "noinitialfocus", "pin", "stayfocused", "tile", "renderunfocused", "persistentsize", -}; -static const auto RULES_PREFIX = std::unordered_set{ - "animation", "bordercolor", "bordersize", "center", "content", "fullscreenstate", "group", "idleinhibit", "maxsize", "minsize", "monitor", - "move", "noclosefor", "opacity", "plugin:", "prop", "pseudo", "rounding", "roundingpower", "scrollmouse", "scrolltouchpad", "size", - "suppressevent", "tag", "workspace", "xray", "novrr", -}; - -CWindowRule::CWindowRule(const std::string& rule, const std::string& value, bool isV2, bool isExecRule) : m_value(value), m_rule(rule), m_v2(isV2), m_execRule(isExecRule) { - const auto VALS = CVarList(rule, 2, ' '); - const bool VALID = RULES.contains(rule) || std::ranges::any_of(RULES_PREFIX, [&rule](auto prefix) { return rule.starts_with(prefix); }) || - (NWindowProperties::boolWindowProperties.contains(VALS[0])) || (NWindowProperties::intWindowProperties.contains(VALS[0])) || - (NWindowProperties::floatWindowProperties.contains(VALS[0])); - - if (!VALID) - return; - - if (rule == "float") - m_ruleType = RULE_FLOAT; - else if (rule == "fullscreen") - m_ruleType = RULE_FULLSCREEN; - else if (rule == "maximize") - m_ruleType = RULE_MAXIMIZE; - else if (rule == "noinitialfocus") - m_ruleType = RULE_NOINITIALFOCUS; - else if (rule == "pin") - m_ruleType = RULE_PIN; - else if (rule == "stayfocused") - m_ruleType = RULE_STAYFOCUSED; - else if (rule == "tile") - m_ruleType = RULE_TILE; - else if (rule == "renderunfocused") - m_ruleType = RULE_RENDERUNFOCUSED; - else if (rule == "persistentsize") - m_ruleType = RULE_PERSISTENTSIZE; - else if (rule.starts_with("animation")) - m_ruleType = RULE_ANIMATION; - else if (rule.starts_with("bordercolor")) - m_ruleType = RULE_BORDERCOLOR; - else if (rule.starts_with("center")) - m_ruleType = RULE_CENTER; - else if (rule.starts_with("fullscreenstate")) - m_ruleType = RULE_FULLSCREENSTATE; - else if (rule.starts_with("group")) - m_ruleType = RULE_GROUP; - else if (rule.starts_with("idleinhibit")) - m_ruleType = RULE_IDLEINHIBIT; - else if (rule.starts_with("maxsize")) - m_ruleType = RULE_MAXSIZE; - else if (rule.starts_with("minsize")) - m_ruleType = RULE_MINSIZE; - else if (rule.starts_with("monitor")) - m_ruleType = RULE_MONITOR; - else if (rule.starts_with("move")) - m_ruleType = RULE_MOVE; - else if (rule.starts_with("opacity")) - m_ruleType = RULE_OPACITY; - else if (rule.starts_with("plugin:")) - m_ruleType = RULE_PLUGIN; - else if (rule.starts_with("pseudo")) - m_ruleType = RULE_PSEUDO; - else if (rule.starts_with("size")) - m_ruleType = RULE_SIZE; - else if (rule.starts_with("suppressevent")) - m_ruleType = RULE_SUPPRESSEVENT; - else if (rule.starts_with("novrr")) - m_ruleType = RULE_NOVRR; - else if (rule.starts_with("tag")) - m_ruleType = RULE_TAG; - else if (rule.starts_with("workspace")) - m_ruleType = RULE_WORKSPACE; - else if (rule.starts_with("prop")) - m_ruleType = RULE_PROP; - else if (rule.starts_with("content")) - m_ruleType = RULE_CONTENT; - else if (rule.starts_with("noclosefor")) - m_ruleType = RULE_NOCLOSEFOR; - else { - // check if this is a prop. - const CVarList VARS(rule, 0, 's', true); - const bool ISPROP = NWindowProperties::intWindowProperties.contains(VARS[0]) || NWindowProperties::boolWindowProperties.contains(VARS[0]) || - NWindowProperties::floatWindowProperties.contains(VARS[0]); - if (ISPROP) { - *const_cast(&m_rule) = "prop " + rule; - m_ruleType = RULE_PROP; - Debug::log(LOG, "CWindowRule: direct prop rule found, rewritten {} -> {}", rule, m_rule); - } else { - Debug::log(ERR, "CWindowRule: didn't match a rule that was found valid?!"); - m_ruleType = RULE_INVALID; - } - } -} diff --git a/src/desktop/WindowRule.hpp b/src/desktop/WindowRule.hpp deleted file mode 100644 index 5bf462e9..00000000 --- a/src/desktop/WindowRule.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include -#include -#include "Rule.hpp" - -class CWindowRule { - public: - CWindowRule(const std::string& rule, const std::string& value, bool isV2 = false, bool isExecRule = false); - - enum eRuleType : uint8_t { - RULE_INVALID = 0, - RULE_FLOAT, - RULE_FULLSCREEN, - RULE_MAXIMIZE, - RULE_NOINITIALFOCUS, - RULE_PIN, - RULE_STAYFOCUSED, - RULE_TILE, - RULE_RENDERUNFOCUSED, - RULE_ANIMATION, - RULE_BORDERCOLOR, - RULE_CENTER, - RULE_FULLSCREENSTATE, - RULE_GROUP, - RULE_IDLEINHIBIT, - RULE_MAXSIZE, - RULE_MINSIZE, - RULE_MONITOR, - RULE_MOVE, - RULE_OPACITY, - RULE_PLUGIN, - RULE_PSEUDO, - RULE_SIZE, - RULE_SUPPRESSEVENT, - RULE_TAG, - RULE_WORKSPACE, - RULE_PROP, - RULE_CONTENT, - RULE_PERSISTENTSIZE, - RULE_NOCLOSEFOR, - RULE_NOVRR, - }; - - eRuleType m_ruleType = RULE_INVALID; - - const std::string m_value; - const std::string m_rule; - const bool m_v2 = false; - const bool m_execRule = false; - - std::string m_title; - std::string m_class; - std::string m_initialTitle; - std::string m_initialClass; - std::string m_tag; - int m_X11 = -1; // -1 means "ANY" - int m_floating = -1; - int m_fullscreen = -1; - int m_pinned = -1; - int m_focus = -1; - int m_group = -1; - int m_modal = -1; - std::string m_fullscreenState = ""; // empty means any - std::string m_onWorkspace = ""; // empty means any - std::string m_workspace = ""; // empty means any - std::string m_contentType = ""; // empty means any - std::string m_xdgTag = ""; // empty means any - - // precompiled regexes - CRuleRegexContainer m_titleRegex; - CRuleRegexContainer m_classRegex; - CRuleRegexContainer m_initialTitleRegex; - CRuleRegexContainer m_initialClassRegex; - CRuleRegexContainer m_v1Regex; -}; diff --git a/src/desktop/Workspace.cpp b/src/desktop/Workspace.cpp index ee35313a..7e1dcd5b 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -542,7 +542,7 @@ void CWorkspace::updateWindows() { if (!w->m_isMapped || w->m_workspace != m_self) continue; - w->updateDynamicRules(); + w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); } } diff --git a/src/desktop/rule/Engine.cpp b/src/desktop/rule/Engine.cpp new file mode 100644 index 00000000..3232035d --- /dev/null +++ b/src/desktop/rule/Engine.cpp @@ -0,0 +1,56 @@ +#include "Engine.hpp" +#include "Rule.hpp" +#include "../LayerSurface.hpp" +#include "../../Compositor.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +SP Rule::ruleEngine() { + static SP engine = makeShared(); + return engine; +} + +void CRuleEngine::registerRule(SP&& 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& 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>& CRuleEngine::rules() { + return m_rules; +} diff --git a/src/desktop/rule/Engine.hpp b/src/desktop/rule/Engine.hpp new file mode 100644 index 00000000..b0ea118e --- /dev/null +++ b/src/desktop/rule/Engine.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "Rule.hpp" + +namespace Desktop::Rule { + class CRuleEngine { + public: + CRuleEngine() = default; + ~CRuleEngine() = default; + + void registerRule(SP&& rule); + void unregisterRule(const std::string& name); + void unregisterRule(const SP& rule); + void updateAllRules(); + void cleanExecRules(); + void clearAllRules(); + const std::vector>& rules(); + + private: + std::vector> m_rules; + }; + + SP ruleEngine(); +} \ No newline at end of file diff --git a/src/desktop/rule/Rule.cpp b/src/desktop/rule/Rule.cpp new file mode 100644 index 00000000..fe7271a6 --- /dev/null +++ b/src/desktop/rule/Rule.cpp @@ -0,0 +1,149 @@ +#include "Rule.hpp" +#include "../../debug/Log.hpp" +#include + +#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 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 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& Rule::allMatchPropStrings() { + static std::vector 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 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 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>(p)); + return; + } + + switch (RULE_ENGINES.at(p)) { + case RULE_MATCH_ENGINE_REGEX: m_matchEngines[p] = makeUnique(s); break; + case RULE_MATCH_ENGINE_BOOL: m_matchEngines[p] = makeUnique(s); break; + case RULE_MATCH_ENGINE_INT: m_matchEngines[p] = makeUnique(s); break; + case RULE_MATCH_ENGINE_WORKSPACE: m_matchEngines[p] = makeUnique(s); break; + case RULE_MATCH_ENGINE_TAG: m_matchEngines[p] = makeUnique(s); break; + } + + m_mask |= p; +} + +std::underlying_type_t 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; +} diff --git a/src/desktop/rule/Rule.hpp b/src/desktop/rule/Rule.hpp new file mode 100644 index 00000000..2b852b3a --- /dev/null +++ b/src/desktop/rule/Rule.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include "matchEngine/MatchEngine.hpp" + +#include "../../helpers/memory/Memory.hpp" +#include "../../helpers/time/Time.hpp" +#include +#include +#include +#include + +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>::max(), + }; + + enum eRuleType : uint8_t { + RULE_TYPE_WINDOW = 0, + RULE_TYPE_LAYER, + }; + + std::optional matchPropFromString(const std::string& s); + std::optional matchPropFromString(const std::string_view& s); + const std::vector& allMatchPropStrings(); + + class IRule { + public: + virtual ~IRule() = default; + + virtual eRuleType type() = 0; + virtual std::underlying_type_t 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> m_matchEngines; + + private: + std::underlying_type_t m_mask = 0; + std::string m_name = ""; + + struct { + bool isExecRule = false; + bool isExecPersistent = false; + std::string token; + Time::steady_tp expiresAt; + } m_execData; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/effect/EffectContainer.hpp b/src/desktop/rule/effect/EffectContainer.hpp new file mode 100644 index 00000000..cb2157a6 --- /dev/null +++ b/src/desktop/rule/effect/EffectContainer.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Desktop::Rule { + template + class IEffectContainer { + static_assert(std::is_enum_v); + + 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) >= 2), std::underlying_type_t, uint16_t>; + + IEffectContainer(std::vector&& 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::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 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& 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 m_keys; + size_t m_originalSize = 0; + }; +}; \ No newline at end of file diff --git a/src/desktop/rule/layerRule/LayerRule.cpp b/src/desktop/rule/layerRule/LayerRule.cpp new file mode 100644 index 00000000..be757672 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRule.cpp @@ -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>& 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>(prop)); + break; + } + + case RULE_PROP_NAMESPACE: + if (!engine->match(ls->m_namespace)) + return false; + break; + } + } + + return true; +} diff --git a/src/desktop/rule/layerRule/LayerRule.hpp b/src/desktop/rule/layerRule/LayerRule.hpp new file mode 100644 index 00000000..990796c1 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRule.hpp @@ -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>& effects(); + + bool matches(PHLLS w); + + private: + std::vector> m_effects; + }; +}; diff --git a/src/desktop/rule/layerRule/LayerRuleApplicator.cpp b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp new file mode 100644 index 00000000..bb7da97f --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp @@ -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 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& 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 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(r); + + if (!wr->matches(m_ls.lock())) + continue; + + applyDynamicRule(wr); + } +} diff --git a/src/desktop/rule/layerRule/LayerRuleApplicator.hpp b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp new file mode 100644 index 00000000..97f15b04 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp @@ -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 props); + void resetProps(std::underlying_type_t props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); + +#define COMMA , +#define DEFINE_PROP(type, name, def) \ + private: \ + std::pair, std::underlying_type_t> m_##name = {def, RULE_PROP_NONE}; \ + \ + public: \ + Types::COverridableVar& name() { \ + return m_##name.first; \ + } \ + void name##Override(const Types::COverridableVar& 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& rule); + }; +}; diff --git a/src/desktop/rule/layerRule/LayerRuleEffectContainer.cpp b/src/desktop/rule/layerRule/LayerRuleEffectContainer.cpp new file mode 100644 index 00000000..17394239 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleEffectContainer.cpp @@ -0,0 +1,33 @@ +#include "LayerRuleEffectContainer.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +// +SP Rule::layerEffects() { + static SP container = makeShared(); + return container; +} + +static const std::vector 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(std::vector{EFFECT_STRINGS}) { + ; +} diff --git a/src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp b/src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp new file mode 100644 index 00000000..e3b3d26c --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp @@ -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 { + public: + CLayerRuleEffectContainer(); + virtual ~CLayerRuleEffectContainer() = default; + }; + + SP layerEffects(); +}; \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/BoolMatchEngine.cpp b/src/desktop/rule/matchEngine/BoolMatchEngine.cpp new file mode 100644 index 00000000..f5c47227 --- /dev/null +++ b/src/desktop/rule/matchEngine/BoolMatchEngine.cpp @@ -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; +} diff --git a/src/desktop/rule/matchEngine/BoolMatchEngine.hpp b/src/desktop/rule/matchEngine/BoolMatchEngine.hpp new file mode 100644 index 00000000..bd162cda --- /dev/null +++ b/src/desktop/rule/matchEngine/BoolMatchEngine.hpp @@ -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; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/IntMatchEngine.cpp b/src/desktop/rule/matchEngine/IntMatchEngine.cpp new file mode 100644 index 00000000..c5bc87f6 --- /dev/null +++ b/src/desktop/rule/matchEngine/IntMatchEngine.cpp @@ -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; +} diff --git a/src/desktop/rule/matchEngine/IntMatchEngine.hpp b/src/desktop/rule/matchEngine/IntMatchEngine.hpp new file mode 100644 index 00000000..2eda492c --- /dev/null +++ b/src/desktop/rule/matchEngine/IntMatchEngine.hpp @@ -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; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/MatchEngine.cpp b/src/desktop/rule/matchEngine/MatchEngine.cpp new file mode 100644 index 00000000..0bc89d7f --- /dev/null +++ b/src/desktop/rule/matchEngine/MatchEngine.cpp @@ -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; +} diff --git a/src/desktop/rule/matchEngine/MatchEngine.hpp b/src/desktop/rule/matchEngine/MatchEngine.hpp new file mode 100644 index 00000000..9588ac05 --- /dev/null +++ b/src/desktop/rule/matchEngine/MatchEngine.hpp @@ -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; + }; +}; \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/RegexMatchEngine.cpp b/src/desktop/rule/matchEngine/RegexMatchEngine.cpp new file mode 100644 index 00000000..14e30af1 --- /dev/null +++ b/src/desktop/rule/matchEngine/RegexMatchEngine.cpp @@ -0,0 +1,17 @@ +#include "RegexMatchEngine.hpp" +#include + +using namespace Desktop::Rule; + +CRegexMatchEngine::CRegexMatchEngine(const std::string& regex) { + if (regex.starts_with("negative:")) { + m_negative = true; + m_regex = makeUnique(regex.substr(9)); + return; + } + m_regex = makeUnique(regex); +} + +bool CRegexMatchEngine::match(const std::string& other) { + return re2::RE2::FullMatch(other, *m_regex) != m_negative; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/RegexMatchEngine.hpp b/src/desktop/rule/matchEngine/RegexMatchEngine.hpp new file mode 100644 index 00000000..e980ce70 --- /dev/null +++ b/src/desktop/rule/matchEngine/RegexMatchEngine.hpp @@ -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 m_regex; + bool m_negative = false; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/TagMatchEngine.cpp b/src/desktop/rule/matchEngine/TagMatchEngine.cpp new file mode 100644 index 00000000..d669822a --- /dev/null +++ b/src/desktop/rule/matchEngine/TagMatchEngine.cpp @@ -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); +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/TagMatchEngine.hpp b/src/desktop/rule/matchEngine/TagMatchEngine.hpp new file mode 100644 index 00000000..f8ef3e22 --- /dev/null +++ b/src/desktop/rule/matchEngine/TagMatchEngine.hpp @@ -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; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp new file mode 100644 index 00000000..abaa1657 --- /dev/null +++ b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp @@ -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); +} diff --git a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp new file mode 100644 index 00000000..c70bc8b4 --- /dev/null +++ b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp @@ -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 = ""; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/utils/SetUtils.hpp b/src/desktop/rule/utils/SetUtils.hpp new file mode 100644 index 00000000..75fd4739 --- /dev/null +++ b/src/desktop/rule/utils/SetUtils.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace Desktop::Rule { + template + bool setsIntersect(const std::unordered_set& A, const std::unordered_set& B) { + if (A.size() > B.size()) + return setsIntersect(B, A); + + for (const auto& e : A) { + if (B.contains(e)) + return true; + } + return false; + } +}; \ No newline at end of file diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp new file mode 100644 index 00000000..b0387b67 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRule.cpp @@ -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 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>& 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>(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::buildFromExecString(std::string&& s) { + CVarList2 varlist(std::move(s), 0, ';'); + SP wr = makeShared("__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::effectsSet() { + return m_effectSet; +} diff --git a/src/desktop/rule/windowRule/WindowRule.hpp b/src/desktop/rule/windowRule/WindowRule.hpp new file mode 100644 index 00000000..944614ce --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRule.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "../Rule.hpp" +#include "../../DesktopTypes.hpp" +#include "WindowRuleEffectContainer.hpp" +#include "../../../helpers/math/Math.hpp" + +#include + +namespace Desktop::Rule { + constexpr const char* EXEC_RULE_ENV_NAME = "HL_EXEC_RULE_TOKEN"; + + std::optional 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 buildFromExecString(std::string&&); + + virtual eRuleType type(); + + void addEffect(storageType e, const std::string& result); + const std::vector>& effects(); + const std::unordered_set& effectsSet(); + + bool matches(PHLWINDOW w, bool allowEnvLookup = false); + + private: + std::vector> m_effects; + std::unordered_set m_effectSet; + }; +}; diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp new file mode 100644 index 00000000..e6e0c655 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp @@ -0,0 +1,642 @@ +#include "WindowRuleApplicator.hpp" +#include "WindowRule.hpp" +#include "../Engine.hpp" +#include "../utils/SetUtils.hpp" +#include "../../Window.hpp" +#include "../../types/OverridableVar.hpp" +#include "../../../managers/LayoutManager.hpp" + +#include + +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 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& 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>(key)); + break; + } + + // custom type, add to our vec + if (!m_otherProps.props.contains(key)) { + m_otherProps.props.emplace(key, + makeUnique(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("misc:size_limits_tiled"); + + if (!m_window) + break; + + if (!m_window->m_isFloating && !sc(*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("misc:size_limits_tiled"); + + if (!m_window) + break; + + if (!m_window->m_isFloating && !sc(*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& rule) { + for (const auto& [key, effect] : rule->effects()) { + switch (key) { + default: { + Debug::log(TRACE, "CWindowRuleApplicator::applyStaticRule: Skipping effect {}, not static", sc>(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> toRemove; + bool tagsWereChanged = false; + + for (const auto& r : ruleEngine()->rules()) { + if (r->type() != RULE_TYPE_WINDOW) + continue; + + auto wr = reinterpretPointerCast(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 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(r); + + if (!wr->matches(m_window.lock(), true)) + continue; + + applyStaticRule(wr); + } + } +} + +void CWindowRuleApplicator::propertiesChanged(std::underlying_type_t props) { + if (!m_window || !m_window->m_isMapped || m_window->isHidden()) + return; + + resetProps(props); + + bool needsRelayout = false; + + std::unordered_set effectsNeedingRecheck; + std::unordered_set> passedWrs; + + for (const auto& r : ruleEngine()->rules()) { + if (r->type() != RULE_TYPE_WINDOW) + continue; + + if (!(r->getPropertiesMask() & props)) + continue; + + auto wr = reinterpretPointerCast(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(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()); +} diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp new file mode 100644 index 00000000..ad80a081 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include + +#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 props); + void resetProps(std::underlying_type_t props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); + void readStaticRules(); + void applyStaticRules(); + + // static props + struct { + std::string monitor, workspace, group; + + std::optional floating; + + bool fullscreen = false; + bool maximize = false; + bool pseudo = false; + bool pin = false; + bool noInitialFocus = false; + + std::optional fullscreenStateClient; + std::optional fullscreenStateInternal; + std::optional center; + std::optional content; + std::optional noCloseFor; + + std::string size, position; + + std::vector suppressEvent; + } static_; + + struct SCustomPropContainer { + CWindowRuleEffectContainer::storageType idx = WINDOW_RULE_EFFECT_NONE; + std::underlying_type_t propMask = RULE_PROP_NONE; + std::string effect; + }; + + // This struct holds props that were dynamically registered. Plugins may read this. + struct { + std::unordered_map> props; + } m_otherProps; + +#define COMMA , +#define DEFINE_PROP(type, name, def) \ + private: \ + std::pair, std::underlying_type_t> m_##name = {def, RULE_PROP_NONE}; \ + \ + public: \ + Types::COverridableVar& name() { \ + return m_##name.first; \ + } \ + void name##Override(const Types::COverridableVar& 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(0) COMMA std::nullopt}) + DEFINE_PROP(Hyprlang::INT, rounding, {std::string("decoration:rounding") COMMA sc(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>> 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& rule); + SRuleResult applyStaticRule(const SP& rule); + }; +}; diff --git a/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp b/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp new file mode 100644 index 00000000..660bf871 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp @@ -0,0 +1,76 @@ +#include "WindowRuleEffectContainer.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +// +SP Rule::windowEffects() { + static SP container = makeShared(); + return container; +} + +static const std::vector 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(std::vector{EFFECT_STRINGS}) { + ; +} diff --git a/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp b/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp new file mode 100644 index 00000000..0827d462 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp @@ -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 { + public: + CWindowRuleEffectContainer(); + virtual ~CWindowRuleEffectContainer() = default; + }; + + SP windowEffects(); +}; \ No newline at end of file diff --git a/src/desktop/types/OverridableVar.hpp b/src/desktop/types/OverridableVar.hpp new file mode 100644 index 00000000..9ecfc890 --- /dev/null +++ b/src/desktop/types/OverridableVar.hpp @@ -0,0 +1,153 @@ +#pragma once + +#include +#include +#include +#include +#include "../../config/ConfigValue.hpp" + +namespace Desktop::Types { + + struct SAlphaValue { + float alpha = 1.F; + bool overridden = false; + + float applyAlpha(float a) const { + if (overridden) + return alpha; + else + return alpha * a; + }; + }; + + enum eOverridePriority : uint8_t { + PRIORITY_LAYOUT = 0, + PRIORITY_WORKSPACE_RULE, + PRIORITY_WINDOW_RULE, + PRIORITY_SET_PROP, + }; + + template + T clampOptional(T const& value, std::optional const& min, std::optional const& max) { + return std::clamp(value, min.value_or(std::numeric_limits::min()), max.value_or(std::numeric_limits::max())); + } + + template || std::is_same_v || std::is_same_v> + class COverridableVar { + public: + COverridableVar(T const& value, eOverridePriority priority) { + m_values[priority] = value; + } + + COverridableVar(T const& value) : m_defaultValue{value} {} + COverridableVar(T const& value, std::optional const& min, std::optional const& max = std::nullopt) : m_defaultValue{value}, m_minValue{min}, m_maxValue{max} {} + COverridableVar(std::string const& value) + requires(Extended && !std::is_same_v) + : m_configValue(SP>(new CConfigValue(value))) {} + COverridableVar(std::string const& value, std::optional const& min, std::optional const& max = std::nullopt) + requires(Extended && !std::is_same_v) + : m_minValue(min), m_maxValue(max), m_configValue(SP>(new CConfigValue(value))) {} + + COverridableVar() = default; + ~COverridableVar() = default; + + COverridableVar& operator=(COverridableVar const& other) { + // Self-assignment check + if (this == &other) + return *this; + + for (auto const& value : other.m_values) { + if constexpr (Extended && !std::is_same_v) + m_values[value.first] = clampOptional(value.second, m_minValue, m_maxValue); + else + m_values[value.first] = value.second; + } + + return *this; + } + + void set(T value, eOverridePriority priority) { + m_values[priority] = value; + } + + void unset(eOverridePriority priority) { + m_values.erase(priority); + } + + bool hasValue() const { + return !m_values.empty(); + } + + T value() const { + if (!m_values.empty()) + return std::prev(m_values.end())->second; + else + throw std::bad_optional_access(); + } + + T valueOr(T const& other) const { + if (hasValue()) + return value(); + else + return other; + } + + T valueOrDefault() const + requires(Extended && !std::is_same_v) + { + if (hasValue()) + return value(); + else if (m_defaultValue.has_value()) + return m_defaultValue.value(); + else + return **std::any_cast>>(m_configValue); + } + + T valueOrDefault() const + requires(!Extended || std::is_same_v) + { + if (hasValue()) + return value(); + else if (!m_defaultValue.has_value()) + throw std::bad_optional_access(); + else + return m_defaultValue.value(); + } + + eOverridePriority getPriority() const { + if (!m_values.empty()) + return std::prev(m_values.end())->first; + else + throw std::bad_optional_access(); + } + + void increment(T const& other, eOverridePriority priority) { + if constexpr (std::is_same_v) + m_values[priority] = valueOr(false) ^ other; + else + m_values[priority] = clampOptional(valueOrDefault() + other, m_minValue, m_maxValue); + } + + void matchOptional(std::optional const& optValue, eOverridePriority priority) { + if (optValue.has_value()) + m_values[priority] = optValue.value(); + else + unset(priority); + } + + operator std::optional() { + if (hasValue()) + return value(); + else + return std::nullopt; + } + + private: + std::map m_values; + std::optional m_defaultValue; // used for toggling, so required for bool + std::optional m_minValue; + std::optional m_maxValue; + std::any m_configValue; // only there for select variables + }; + +} \ No newline at end of file diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index 9e3b49d4..dfbb9f2e 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -135,31 +135,23 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_X11ShouldntFocus = PWINDOW->m_X11ShouldntFocus || (PWINDOW->m_isX11 && PWINDOW->isX11OverrideRedirect() && !PWINDOW->m_xwaylandSurface->wantsFocus()); // window rules - PWINDOW->m_matchedRules = g_pConfigManager->getMatchingRules(PWINDOW, false); std::optional requestedInternalFSMode, requestedClientFSMode; std::optional requestedFSState; if (PWINDOW->m_wantsInitialFullscreen || (PWINDOW->m_isX11 && PWINDOW->m_xwaylandSurface->m_fullscreen)) requestedClientFSMode = FSMODE_FULLSCREEN; MONITORID requestedFSMonitor = PWINDOW->m_wantsInitialFullscreenMonitor; - for (auto const& r : PWINDOW->m_matchedRules) { - switch (r->m_ruleType) { - case CWindowRule::RULE_MONITOR: { - try { - const auto MONITORSTR = trim(r->m_rule.substr(r->m_rule.find(' '))); + PWINDOW->m_ruleApplicator->readStaticRules(); + { + if (!PWINDOW->m_ruleApplicator->static_.monitor.empty()) { + const auto& MONITORSTR = PWINDOW->m_ruleApplicator->static_.monitor; + if (MONITORSTR == "unset") + PWINDOW->m_monitor = PMONITOR; + else { + const auto MONITOR = g_pCompositor->getMonitorFromString(MONITORSTR); - if (MONITORSTR == "unset") - PWINDOW->m_monitor = PMONITOR; - else { - const auto MONITOR = g_pCompositor->getMonitorFromString(MONITORSTR); - - if (MONITOR) - PWINDOW->m_monitor = MONITOR; - else { - Debug::log(ERR, "No monitor in monitor {} rule", MONITORSTR); - continue; - } - } + if (MONITOR) { + PWINDOW->m_monitor = MONITOR; const auto PMONITORFROMID = PWINDOW->m_monitor.lock(); @@ -172,100 +164,73 @@ void Events::listener_mapWindow(void* owner, void* data) { Debug::log(LOG, "Rule monitor, applying to {:mw}", PWINDOW); requestedFSMonitor = MONITOR_INVALID; - } catch (std::exception& e) { Debug::log(ERR, "Rule monitor failed, rule: {} -> {} | err: {}", r->m_rule, r->m_value, e.what()); } - break; + } else + Debug::log(ERR, "No monitor in monitor {} rule", MONITORSTR); } - case CWindowRule::RULE_WORKSPACE: { - // check if it isn't unset - const auto WORKSPACERQ = r->m_rule.substr(r->m_rule.find_first_of(' ') + 1); + } - if (WORKSPACERQ == "unset") - requestedWorkspace = ""; + if (!PWINDOW->m_ruleApplicator->static_.workspace.empty()) { + const auto WORKSPACERQ = PWINDOW->m_ruleApplicator->static_.workspace; + + if (WORKSPACERQ == "unset") + requestedWorkspace = ""; + else + requestedWorkspace = WORKSPACERQ; + + const auto JUSTWORKSPACE = WORKSPACERQ.contains(' ') ? WORKSPACERQ.substr(0, WORKSPACERQ.find_first_of(' ')) : WORKSPACERQ; + + if (JUSTWORKSPACE == PWORKSPACE->m_name || JUSTWORKSPACE == "name:" + PWORKSPACE->m_name) + requestedWorkspace = ""; + + Debug::log(LOG, "Rule workspace matched by {}, {} applied.", PWINDOW, PWINDOW->m_ruleApplicator->static_.workspace); + requestedFSMonitor = MONITOR_INVALID; + } + + if (PWINDOW->m_ruleApplicator->static_.floating.has_value()) + PWINDOW->m_isFloating = PWINDOW->m_ruleApplicator->static_.floating.value(); + + if (PWINDOW->m_ruleApplicator->static_.pseudo) + PWINDOW->m_isPseudotiled = true; + + if (PWINDOW->m_ruleApplicator->static_.noInitialFocus) + PWINDOW->m_noInitialFocus = true; + + if (PWINDOW->m_ruleApplicator->static_.fullscreenStateClient || PWINDOW->m_ruleApplicator->static_.fullscreenStateInternal) { + requestedFSState = SFullscreenState{ + .internal = sc(PWINDOW->m_ruleApplicator->static_.fullscreenStateInternal.value_or(0)), + .client = sc(PWINDOW->m_ruleApplicator->static_.fullscreenStateClient.value_or(0)), + }; + } + + if (!PWINDOW->m_ruleApplicator->static_.suppressEvent.empty()) { + for (const auto& var : PWINDOW->m_ruleApplicator->static_.suppressEvent) { + if (var == "fullscreen") + PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN; + else if (var == "maximize") + PWINDOW->m_suppressedEvents |= SUPPRESS_MAXIMIZE; + else if (var == "activate") + PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE; + else if (var == "activatefocus") + PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE_FOCUSONLY; + else if (var == "fullscreenoutput") + PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN_OUTPUT; else - requestedWorkspace = WORKSPACERQ; + Debug::log(ERR, "Error while parsing suppressevent windowrule: unknown event type {}", var); + } + } - const auto JUSTWORKSPACE = WORKSPACERQ.contains(' ') ? WORKSPACERQ.substr(0, WORKSPACERQ.find_first_of(' ')) : WORKSPACERQ; + if (PWINDOW->m_ruleApplicator->static_.pin) + PWINDOW->m_pinned = true; - if (JUSTWORKSPACE == PWORKSPACE->m_name || JUSTWORKSPACE == "name:" + PWORKSPACE->m_name) - requestedWorkspace = ""; + if (PWINDOW->m_ruleApplicator->static_.fullscreen) + requestedInternalFSMode = FSMODE_FULLSCREEN; - Debug::log(LOG, "Rule workspace matched by {}, {} applied.", PWINDOW, r->m_value); - requestedFSMonitor = MONITOR_INVALID; - break; - } - case CWindowRule::RULE_FLOAT: { - PWINDOW->m_isFloating = true; - break; - } - case CWindowRule::RULE_TILE: { - PWINDOW->m_isFloating = false; - break; - } - case CWindowRule::RULE_PSEUDO: { - PWINDOW->m_isPseudotiled = true; - break; - } - case CWindowRule::RULE_NOINITIALFOCUS: { - PWINDOW->m_noInitialFocus = true; - break; - } - case CWindowRule::RULE_FULLSCREENSTATE: { - const auto ARGS = CVarList(r->m_rule.substr(r->m_rule.find_first_of(' ') + 1), 2, ' '); - int internalMode, clientMode; - try { - internalMode = std::stoi(ARGS[0]); - } catch (std::exception& e) { internalMode = 0; } - try { - clientMode = std::stoi(ARGS[1]); - } catch (std::exception& e) { clientMode = 0; } - requestedFSState = SFullscreenState{.internal = sc(internalMode), .client = sc(clientMode)}; - break; - } - case CWindowRule::RULE_SUPPRESSEVENT: { - CVarList vars(r->m_rule, 0, 's', true); - for (size_t i = 1; i < vars.size(); ++i) { - if (vars[i] == "fullscreen") - PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN; - else if (vars[i] == "maximize") - PWINDOW->m_suppressedEvents |= SUPPRESS_MAXIMIZE; - else if (vars[i] == "activate") - PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE; - else if (vars[i] == "activatefocus") - PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE_FOCUSONLY; - else if (vars[i] == "fullscreenoutput") - PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN_OUTPUT; - else - Debug::log(ERR, "Error while parsing suppressevent windowrule: unknown event type {}", vars[i]); - } - break; - } - case CWindowRule::RULE_PIN: { - PWINDOW->m_pinned = true; - break; - } - case CWindowRule::RULE_FULLSCREEN: { - requestedInternalFSMode = FSMODE_FULLSCREEN; - break; - } - case CWindowRule::RULE_MAXIMIZE: { - requestedInternalFSMode = FSMODE_MAXIMIZED; - break; - } - case CWindowRule::RULE_STAYFOCUSED: { - PWINDOW->m_stayFocused = true; - break; - } - case CWindowRule::RULE_GROUP: { - if (PWINDOW->m_groupRules & GROUP_OVERRIDE) - continue; + if (PWINDOW->m_ruleApplicator->static_.maximize) + requestedInternalFSMode = FSMODE_MAXIMIZED; - // `group` is a shorthand of `group set` - if (trim(r->m_rule) == "group") { - PWINDOW->m_groupRules |= GROUP_SET; - continue; - } - - CVarList vars(r->m_rule, 0, 's'); + if (!PWINDOW->m_ruleApplicator->static_.group.empty()) { + if (!(PWINDOW->m_groupRules & GROUP_OVERRIDE) && trim(PWINDOW->m_ruleApplicator->static_.group) != "group") { + CVarList2 vars(std::string{PWINDOW->m_ruleApplicator->static_.group}, 0, 's'); std::string vPrev = ""; for (auto const& v : vars) { @@ -302,26 +267,14 @@ void Events::listener_mapWindow(void* owner, void* data) { } vPrev = v; } - break; } - case CWindowRule::RULE_CONTENT: { - const CVarList VARS(r->m_rule, 0, ' '); - try { - PWINDOW->setContentType(NContentType::fromString(VARS[1])); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_NOCLOSEFOR: { - const CVarList VARS(r->m_rule, 0, ' '); - try { - PWINDOW->m_closeableSince = Time::steadyNow() + std::chrono::milliseconds(std::stoull(VARS[1])); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - default: break; } - PWINDOW->applyDynamicRule(r); + if (PWINDOW->m_ruleApplicator->static_.content) + PWINDOW->setContentType(sc(PWINDOW->m_ruleApplicator->static_.content.value())); + + if (PWINDOW->m_ruleApplicator->static_.noCloseFor) + PWINDOW->m_closeableSince = Time::steadyNow() + std::chrono::milliseconds(PWINDOW->m_ruleApplicator->static_.noCloseFor.value()); } // make it uncloseable if it's a Hyprland dialog @@ -333,7 +286,7 @@ void Events::listener_mapWindow(void* owner, void* data) { if (PWINDOW->m_pinned && !PWINDOW->m_isFloating) PWINDOW->m_pinned = false; - CVarList WORKSPACEARGS = CVarList(requestedWorkspace, 0, ' '); + CVarList2 WORKSPACEARGS = CVarList2(std::move(requestedWorkspace), 0, ' ', false, false); if (!WORKSPACEARGS[0].empty()) { WORKSPACEID requestedWorkspaceID; @@ -420,140 +373,31 @@ void Events::listener_mapWindow(void* owner, void* data) { g_pLayoutManager->getCurrentLayout()->onWindowCreated(PWINDOW); PWINDOW->m_createdOverFullscreen = true; - // size and move rules - for (auto const& r : PWINDOW->m_matchedRules) { - switch (r->m_ruleType) { - case CWindowRule::RULE_SIZE: { - try { - auto stringToFloatClamp = [](const std::string& VALUE, const float CURR, const float REL) { - if (VALUE.starts_with('<')) - return std::min(CURR, stringToPercentage(VALUE.substr(1, VALUE.length() - 1), REL)); - else if (VALUE.starts_with('>')) - return std::max(CURR, stringToPercentage(VALUE.substr(1, VALUE.length() - 1), REL)); - - return stringToPercentage(VALUE, REL); - }; - - const auto VALUE = r->m_rule.substr(r->m_rule.find(' ') + 1); - const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' ')); - const auto SIZEYSTR = VALUE.substr(VALUE.find(' ') + 1); - - const auto MAXSIZE = PWINDOW->requestedMaxSize(); - - const float SIZEX = SIZEXSTR == "max" ? std::clamp(MAXSIZE.x, MIN_WINDOW_SIZE, PMONITOR->m_size.x) : - stringToFloatClamp(SIZEXSTR, PWINDOW->m_realSize->goal().x, PMONITOR->m_size.x); - - const float SIZEY = SIZEYSTR == "max" ? std::clamp(MAXSIZE.y, MIN_WINDOW_SIZE, PMONITOR->m_size.y) : - stringToFloatClamp(SIZEYSTR, PWINDOW->m_realSize->goal().y, PMONITOR->m_size.y); - - Debug::log(LOG, "Rule size, applying to {}", PWINDOW); - - PWINDOW->clampWindowSize(Vector2D{SIZEXSTR.starts_with("<") ? 0 : SIZEX, SIZEYSTR.starts_with("<") ? 0 : SIZEY}, Vector2D{SIZEX, SIZEY}); - - PWINDOW->setHidden(false); - } catch (...) { Debug::log(LOG, "Rule size failed, rule: {} -> {}", r->m_rule, r->m_value); } - break; - } - case CWindowRule::RULE_MOVE: { - try { - auto value = r->m_rule.substr(r->m_rule.find(' ') + 1); - - const bool ONSCREEN = value.starts_with("onscreen"); - - if (ONSCREEN) - value = value.substr(value.find_first_of(' ') + 1); - - const bool CURSOR = value.starts_with("cursor"); - - if (CURSOR) - value = value.substr(value.find_first_of(' ') + 1); - - const auto POSXSTR = value.substr(0, value.find(' ')); - const auto POSYSTR = value.substr(value.find(' ') + 1); - - int posX = 0; - int posY = 0; - - if (POSXSTR.starts_with("100%-")) { - const bool subtractWindow = POSXSTR.starts_with("100%-w-"); - const auto POSXRAW = (subtractWindow) ? POSXSTR.substr(7) : POSXSTR.substr(5); - posX = - PMONITOR->m_size.x - (!POSXRAW.contains('%') ? std::stoi(POSXRAW) : std::stof(POSXRAW.substr(0, POSXRAW.length() - 1)) * 0.01 * PMONITOR->m_size.x); - - if (subtractWindow) - posX -= PWINDOW->m_realSize->goal().x; - - if (CURSOR) - Debug::log(ERR, "Cursor is not compatible with 100%-, ignoring cursor!"); - } else if (!CURSOR) { - posX = !POSXSTR.contains('%') ? std::stoi(POSXSTR) : std::stof(POSXSTR.substr(0, POSXSTR.length() - 1)) * 0.01 * PMONITOR->m_size.x; - } else { - // cursor - if (POSXSTR == "cursor") { - posX = g_pInputManager->getMouseCoordsInternal().x - PMONITOR->m_position.x; - } else { - posX = g_pInputManager->getMouseCoordsInternal().x - PMONITOR->m_position.x + - (!POSXSTR.contains('%') ? std::stoi(POSXSTR) : std::stof(POSXSTR.substr(0, POSXSTR.length() - 1)) * 0.01 * PWINDOW->m_realSize->goal().x); - } - } - - if (POSYSTR.starts_with("100%-")) { - const bool subtractWindow = POSYSTR.starts_with("100%-h-"); - const auto POSYRAW = (subtractWindow) ? POSYSTR.substr(7) : POSYSTR.substr(5); - posY = - PMONITOR->m_size.y - (!POSYRAW.contains('%') ? std::stoi(POSYRAW) : std::stof(POSYRAW.substr(0, POSYRAW.length() - 1)) * 0.01 * PMONITOR->m_size.y); - - if (subtractWindow) - posY -= PWINDOW->m_realSize->goal().y; - - if (CURSOR) - Debug::log(ERR, "Cursor is not compatible with 100%-, ignoring cursor!"); - } else if (!CURSOR) { - posY = !POSYSTR.contains('%') ? std::stoi(POSYSTR) : std::stof(POSYSTR.substr(0, POSYSTR.length() - 1)) * 0.01 * PMONITOR->m_size.y; - } else { - // cursor - if (POSYSTR == "cursor") { - posY = g_pInputManager->getMouseCoordsInternal().y - PMONITOR->m_position.y; - } else { - posY = g_pInputManager->getMouseCoordsInternal().y - PMONITOR->m_position.y + - (!POSYSTR.contains('%') ? std::stoi(POSYSTR) : std::stof(POSYSTR.substr(0, POSYSTR.length() - 1)) * 0.01 * PWINDOW->m_realSize->goal().y); - } - } - - if (ONSCREEN) { - int borderSize = PWINDOW->getRealBorderSize(); - - posX = std::clamp(posX, sc(PMONITOR->m_reservedTopLeft.x + borderSize), - std::max(sc(PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x - PWINDOW->m_realSize->goal().x - borderSize), - sc(PMONITOR->m_reservedTopLeft.x + borderSize + 1))); - - posY = std::clamp(posY, sc(PMONITOR->m_reservedTopLeft.y + borderSize), - std::max(sc(PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y - PWINDOW->m_realSize->goal().y - borderSize), - sc(PMONITOR->m_reservedTopLeft.y + borderSize + 1))); - } - - Debug::log(LOG, "Rule move, applying to {}", PWINDOW); - - *PWINDOW->m_realPosition = Vector2D(posX, posY) + PMONITOR->m_position; - - PWINDOW->setHidden(false); - } catch (...) { Debug::log(LOG, "Rule move failed, rule: {} -> {}", r->m_rule, r->m_value); } - break; - } - case CWindowRule::RULE_CENTER: { - auto RESERVEDOFFSET = Vector2D(); - const auto ARGS = CVarList(r->m_rule, 2, ' '); - if (ARGS[1] == "1") - RESERVEDOFFSET = (PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight) / 2.f; - - *PWINDOW->m_realPosition = PMONITOR->middle() - PWINDOW->m_realSize->goal() / 2.f + RESERVEDOFFSET; - break; - } - - default: break; + if (!PWINDOW->m_ruleApplicator->static_.size.empty()) { + const auto COMPUTED = PWINDOW->calculateExpression(PWINDOW->m_ruleApplicator->static_.size); + if (!COMPUTED) + Debug::log(ERR, "failed to parse {} as an expression", PWINDOW->m_ruleApplicator->static_.size); + else { + *PWINDOW->m_realSize = *COMPUTED; + PWINDOW->setHidden(false); } } + if (!PWINDOW->m_ruleApplicator->static_.position.empty()) { + const auto COMPUTED = PWINDOW->calculateExpression(PWINDOW->m_ruleApplicator->static_.position); + if (!COMPUTED) + Debug::log(ERR, "failed to parse {} as an expression", PWINDOW->m_ruleApplicator->static_.position); + else { + *PWINDOW->m_realPosition = *COMPUTED + PMONITOR->m_position; + PWINDOW->setHidden(false); + } + } + + if (PWINDOW->m_ruleApplicator->static_.center) { + auto RESERVEDOFFSET = (PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight) / 2.f; + *PWINDOW->m_realPosition = PMONITOR->middle() - PWINDOW->m_realSize->goal() / 2.f + RESERVEDOFFSET; + } + // set the pseudo size to the GOAL of our current size // because the windows are animated on RealSize PWINDOW->m_pseudoSize = PWINDOW->m_realSize->goal(); @@ -564,28 +408,15 @@ void Events::listener_mapWindow(void* owner, void* data) { bool setPseudo = false; - for (auto const& r : PWINDOW->m_matchedRules) { - if (r->m_ruleType != CWindowRule::RULE_SIZE) - continue; - - try { - const auto VALUE = r->m_rule.substr(r->m_rule.find(' ') + 1); - const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' ')); - const auto SIZEYSTR = VALUE.substr(VALUE.find(' ') + 1); - - const auto MAXSIZE = PWINDOW->requestedMaxSize(); - - const float SIZEX = SIZEXSTR == "max" ? std::clamp(MAXSIZE.x, MIN_WINDOW_SIZE, PMONITOR->m_size.x) : stringToPercentage(SIZEXSTR, PMONITOR->m_size.x); - - const float SIZEY = SIZEYSTR == "max" ? std::clamp(MAXSIZE.y, MIN_WINDOW_SIZE, PMONITOR->m_size.y) : stringToPercentage(SIZEYSTR, PMONITOR->m_size.y); - - Debug::log(LOG, "Rule size (tiled), applying to {}", PWINDOW); - + if (!PWINDOW->m_ruleApplicator->static_.size.empty()) { + const auto COMPUTED = PWINDOW->calculateExpression(PWINDOW->m_ruleApplicator->static_.size); + if (!COMPUTED) + Debug::log(ERR, "failed to parse {} as an expression", PWINDOW->m_ruleApplicator->static_.size); + else { setPseudo = true; - PWINDOW->m_pseudoSize = Vector2D(SIZEX, SIZEY); - + PWINDOW->m_pseudoSize = *COMPUTED; PWINDOW->setHidden(false); - } catch (...) { Debug::log(LOG, "Rule size failed, rule: {} -> {}", r->m_rule, r->m_value); } + } } if (!setPseudo) @@ -594,10 +425,10 @@ void Events::listener_mapWindow(void* owner, void* data) { const auto PFOCUSEDWINDOWPREV = g_pCompositor->m_lastWindow.lock(); - if (PWINDOW->m_windowData.allowsInput.valueOrDefault()) { // if default value wasn't set to false getPriority() would throw an exception - PWINDOW->m_windowData.noFocus = CWindowOverridableVar(false, PWINDOW->m_windowData.allowsInput.getPriority()); - PWINDOW->m_noInitialFocus = false; - PWINDOW->m_X11ShouldntFocus = false; + if (PWINDOW->m_ruleApplicator->allowsInput().valueOrDefault()) { // if default value wasn't set to false getPriority() would throw an exception + PWINDOW->m_ruleApplicator->noFocusOverride(Desktop::Types::COverridableVar(false, PWINDOW->m_ruleApplicator->allowsInput().getPriority())); + PWINDOW->m_noInitialFocus = false; + PWINDOW->m_X11ShouldntFocus = false; } // check LS focus grab @@ -615,12 +446,12 @@ void Events::listener_mapWindow(void* owner, void* data) { g_pCompositor->setWindowFullscreenInternal(PWINDOW->m_workspace->getFullscreenWindow(), FSMODE_NONE); } - if (!PWINDOW->m_windowData.noFocus.valueOrDefault() && !PWINDOW->m_noInitialFocus && + if (!PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() && !PWINDOW->m_noInitialFocus && (!PWINDOW->isX11OverrideRedirect() || (PWINDOW->m_isX11 && PWINDOW->m_xwaylandSurface->wantsFocus())) && !workspaceSilent && (!PFORCEFOCUS || PFORCEFOCUS == PWINDOW) && !g_pInputManager->isConstrained()) { g_pCompositor->focusWindow(PWINDOW); PWINDOW->m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA); - PWINDOW->m_dimPercent->setValueAndWarp(PWINDOW->m_windowData.noDim.valueOrDefault() ? 0.f : *PDIMSTRENGTH); + PWINDOW->m_dimPercent->setValueAndWarp(PWINDOW->m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH); } else { PWINDOW->m_activeInactiveAlpha->setValueAndWarp(*PINACTIVEALPHA); PWINDOW->m_dimPercent->setValueAndWarp(0); @@ -639,9 +470,9 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_realPosition->warp(); PWINDOW->m_realSize->warp(); if (requestedFSState.has_value()) { - PWINDOW->m_windowData.syncFullscreen = CWindowOverridableVar(false, PRIORITY_WINDOW_RULE); + PWINDOW->m_ruleApplicator->syncFullscreenOverride(Desktop::Types::COverridableVar(false, Desktop::Types::PRIORITY_WINDOW_RULE)); g_pCompositor->setWindowFullscreenState(PWINDOW, requestedFSState.value()); - } else if (requestedInternalFSMode.has_value() && requestedClientFSMode.has_value() && !PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) + } else if (requestedInternalFSMode.has_value() && requestedClientFSMode.has_value() && !PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) g_pCompositor->setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = requestedInternalFSMode.value(), .client = requestedClientFSMode.value()}); else if (requestedInternalFSMode.has_value()) g_pCompositor->setWindowFullscreenInternal(PWINDOW, requestedInternalFSMode.value()); @@ -653,6 +484,7 @@ void Events::listener_mapWindow(void* owner, void* data) { g_pInputManager->recheckIdleInhibitorStatus(); PWINDOW->updateToplevel(); + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ALL); if (workspaceSilent) { if (validMapped(PFOCUSEDWINDOWPREV)) { @@ -689,7 +521,7 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_realSize->setCallbackOnEnd(setVector2DAnimToMove); // recalc the values for this window - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); // avoid this window being visible if (PWORKSPACE->m_hasFullscreenWindow && !PWINDOW->isFullscreen() && !PWINDOW->m_isFloating) PWINDOW->m_alpha->setValueAndWarp(0.f); @@ -736,8 +568,7 @@ void Events::listener_unmapWindow(void* owner, void* data) { g_pEventManager->postEvent(SHyprIPCEvent{"closewindow", std::format("{:x}", PWINDOW)}); EMIT_HOOK_EVENT("closeWindow", PWINDOW); - if (PWINDOW->m_isFloating && !PWINDOW->m_isX11 && - std::ranges::any_of(PWINDOW->m_matchedRules, [](const auto& r) { return r->m_ruleType == CWindowRule::RULE_PERSISTENTSIZE; })) { + if (PWINDOW->m_isFloating && !PWINDOW->m_isX11 && PWINDOW->m_ruleApplicator->persistentSize().valueOrDefault()) { Debug::log(LOG, "storing floating size {}x{} for window {}::{} on close", PWINDOW->m_realSize->value().x, PWINDOW->m_realSize->value().y, PWINDOW->m_class, PWINDOW->m_title); g_pConfigManager->storeFloatingSize(PWINDOW, PWINDOW->m_realSize->value()); diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index 4cf3c671..d11e1be6 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -975,3 +975,13 @@ std::string getBuiltSystemLibraryNames() { result += std::format("Aquamarine: built against {}, system has {}\n", AQUAMARINE_VERSION, getSystemLibraryVersion("aquamarine")); return result; } + +bool truthy(const std::string& str) { + if (str == "1") + return true; + + std::string cpy = str; + std::ranges::transform(cpy, cpy.begin(), ::tolower); + + return cpy.starts_with("true") || cpy.starts_with("yes") || cpy.starts_with("on"); +} diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index 5feb2de9..183b6fac 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -46,6 +46,7 @@ std::expected binaryNameForPid(pid_t pid); std::string deviceNameToInternalString(std::string in); std::string getSystemLibraryVersion(const std::string& name); std::string getBuiltSystemLibraryNames(); +bool truthy(const std::string& str); template [[deprecated("use std::format instead")]] std::string getFormat(std::format_string fmt, Args&&... args) { diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 1a7b4ac6..ce9c6990 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -177,7 +177,11 @@ void CMonitor::onConnect(bool noRule) { m_forceSize = SIZE; SMonitorRule rule = m_activeMonitorRule; - rule.resolution = SIZE; + + if (SIZE == rule.resolution) + return; + + rule.resolution = SIZE; applyMonitorRule(&rule); }); diff --git a/src/helpers/TagKeeper.cpp b/src/helpers/TagKeeper.cpp index 3c7071d5..7f377657 100644 --- a/src/helpers/TagKeeper.cpp +++ b/src/helpers/TagKeeper.cpp @@ -1,6 +1,6 @@ #include "TagKeeper.hpp" -bool CTagKeeper::isTagged(const std::string& tag, bool strict) { +bool CTagKeeper::isTagged(const std::string& tag, bool strict) const { const bool NEGATIVE = tag.starts_with("negative"); const auto MATCH = NEGATIVE ? tag.substr(9) : tag; const bool TAGGED = m_tags.contains(MATCH) || (!strict && m_tags.contains(MATCH + "*")); @@ -38,6 +38,6 @@ bool CTagKeeper::applyTag(const std::string& tag, bool dynamic) { return true; } -bool CTagKeeper::removeDynamicTags() { - return std::erase_if(m_tags, [](const auto& tag) { return tag.ends_with("*"); }); +bool CTagKeeper::removeDynamicTag(const std::string& s) { + return std::erase_if(m_tags, [&s](const auto& tag) { return tag == s + "*"; }); } diff --git a/src/helpers/TagKeeper.hpp b/src/helpers/TagKeeper.hpp index f4732005..d18a0d29 100644 --- a/src/helpers/TagKeeper.hpp +++ b/src/helpers/TagKeeper.hpp @@ -5,9 +5,9 @@ class CTagKeeper { public: - bool isTagged(const std::string& tag, bool strict = false); + bool isTagged(const std::string& tag, bool strict = false) const; bool applyTag(const std::string& tag, bool dynamic = false); - bool removeDynamicTags(); + bool removeDynamicTag(const std::string& tag); const auto& getTags() const { return m_tags; diff --git a/src/helpers/math/Expression.cpp b/src/helpers/math/Expression.cpp new file mode 100644 index 00000000..fb28628d --- /dev/null +++ b/src/helpers/math/Expression.cpp @@ -0,0 +1,22 @@ +#include "Expression.hpp" +#include "muParser.h" +#include "../../debug/Log.hpp" + +using namespace Math; + +CExpression::CExpression() : m_parser(makeUnique()) { + ; +} + +void CExpression::addVariable(const std::string& name, double val) { + m_parser->DefineConst(name, val); +} + +std::optional CExpression::compute(const std::string& expr) { + try { + m_parser->SetExpr(expr); + return m_parser->Eval(); + } catch (mu::Parser::exception_type& e) { Debug::log(ERR, "CExpression::compute: mu threw: {}", e.GetMsg()); } + + return std::nullopt; +} diff --git a/src/helpers/math/Expression.hpp b/src/helpers/math/Expression.hpp new file mode 100644 index 00000000..1780e3ee --- /dev/null +++ b/src/helpers/math/Expression.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../memory/Memory.hpp" +#include +#include + +namespace mu { + class Parser; +}; + +namespace Math { + class CExpression { + public: + CExpression(); + ~CExpression() = default; + + CExpression(const CExpression&) = delete; + CExpression(CExpression&) = delete; + CExpression(CExpression&&) = delete; + + void addVariable(const std::string& name, double val); + + std::optional compute(const std::string& expr); + + private: + UP m_parser; + }; +}; \ No newline at end of file diff --git a/src/helpers/varlist/VarList.hpp b/src/helpers/varlist/VarList.hpp index 4cdc1728..ca68751e 100644 --- a/src/helpers/varlist/VarList.hpp +++ b/src/helpers/varlist/VarList.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include //NOLINTNEXTLINE using namespace Hyprutils::String; diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp index 6df54445..c653a47e 100644 --- a/src/layout/DwindleLayout.cpp +++ b/src/layout/DwindleLayout.cpp @@ -183,7 +183,7 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for if (PWINDOW->isFullscreen() && !pNode->ignoreFullscreenChecks) return; - PWINDOW->unsetWindowData(PRIORITY_LAYOUT); + PWINDOW->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); PWINDOW->updateWindowData(); static auto PGAPSINDATA = CConfigValue("general:gaps_in"); @@ -272,10 +272,10 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for Vector2D monitorAvailable = PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight - Vector2D{(double)(gapsOut.m_left + gapsOut.m_right), (double)(gapsOut.m_top + gapsOut.m_bottom)} - Vector2D{2.0 * borderSize, 2.0 * borderSize}; - Vector2D minSize = PWINDOW->m_windowData.minSize.valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); - Vector2D maxSize = - PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : PWINDOW->m_windowData.maxSize.valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable); - calcSize = calcSize.clamp(minSize, maxSize); + Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); + Vector2D maxSize = PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable); + calcSize = calcSize.clamp(minSize, maxSize); calcPos += (availableSpace - calcSize) / 2.0; @@ -547,7 +547,7 @@ void CHyprDwindleLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { return; } - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); if (pWindow->isFullscreen()) @@ -664,9 +664,9 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn const auto PNODE = getNodeFromWindow(PWINDOW); if (!PNODE) { - *PWINDOW->m_realSize = - (PWINDOW->m_realSize->goal() + pixResize) - .clamp(PWINDOW->m_windowData.minSize.valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), PWINDOW->m_windowData.maxSize.valueOr(Vector2D{INFINITY, INFINITY})); + *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + pixResize) + .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY})); PWINDOW->updateWindowDecos(); return; } @@ -709,8 +709,8 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn CBox wbox = PNODE->box; wbox.round(); - Vector2D minSize = PWINDOW->m_windowData.minSize.valueOr(Vector2D{30.0, 30.0}); - Vector2D maxSize = PWINDOW->m_windowData.maxSize.valueOr(Vector2D{INFINITY, INFINITY}); + Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{30.0, 30.0}); + Vector2D maxSize = PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}); Vector2D upperBound = Vector2D{std::min(maxSize.x, wbox.w), std::min(maxSize.y, wbox.h)}; PWINDOW->m_pseudoSize = PWINDOW->m_pseudoSize.clamp(minSize, upperBound); @@ -870,7 +870,7 @@ void CHyprDwindleLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFu *pWindow->m_realPosition = pWindow->m_lastFloatingPosition; *pWindow->m_realSize = pWindow->m_lastFloatingSize; - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); } } else { diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index 702d6ac9..85b401bd 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -13,11 +13,12 @@ #include "../managers/EventManager.hpp" #include "../managers/HookSystemManager.hpp" #include "../managers/cursor/CursorShapeOverrideController.hpp" +#include "../desktop/rule/windowRule/WindowRule.hpp" void IHyprLayout::onWindowCreated(PHLWINDOW pWindow, eDirection direction) { CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(pWindow); - const bool HASPERSISTENTSIZE = std::ranges::any_of(pWindow->m_matchedRules, [](const auto& rule) { return rule->m_ruleType == CWindowRule::RULE_PERSISTENTSIZE; }); + const bool HASPERSISTENTSIZE = pWindow->m_ruleApplicator->persistentSize().valueOrDefault(); const auto STOREDSIZE = HASPERSISTENTSIZE ? g_pConfigManager->getStoredFloatingSize(pWindow) : std::nullopt; @@ -77,7 +78,7 @@ void IHyprLayout::onWindowRemoved(PHLWINDOW pWindow) { pWindow->updateWindowDecos(); PWINDOWPREV->getGroupCurrent()->updateWindowDecos(); - g_pCompositor->updateWindowAnimatedDecorationValues(pWindow); + pWindow->updateDecorationValues(); return; } @@ -637,10 +638,10 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) { } else if (g_pInputManager->m_dragMode == MBIND_RESIZE || g_pInputManager->m_dragMode == MBIND_RESIZE_FORCE_RATIO || g_pInputManager->m_dragMode == MBIND_RESIZE_BLOCK_RATIO) { if (DRAGGINGWINDOW->m_isFloating) { - Vector2D MINSIZE = DRAGGINGWINDOW->requestedMinSize().clamp(DRAGGINGWINDOW->m_windowData.minSize.valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE))); + Vector2D MINSIZE = DRAGGINGWINDOW->requestedMinSize().clamp(DRAGGINGWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE))); Vector2D MAXSIZE; - if (DRAGGINGWINDOW->m_windowData.maxSize.hasValue()) - MAXSIZE = DRAGGINGWINDOW->requestedMaxSize().clamp({}, DRAGGINGWINDOW->m_windowData.maxSize.value()); + if (DRAGGINGWINDOW->m_ruleApplicator->maxSize().hasValue()) + MAXSIZE = DRAGGINGWINDOW->requestedMaxSize().clamp({}, DRAGGINGWINDOW->m_ruleApplicator->maxSize().value()); else MAXSIZE = DRAGGINGWINDOW->requestedMaxSize().clamp({}, Vector2D(std::numeric_limits::max(), std::numeric_limits::max())); @@ -657,7 +658,7 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) { newSize = newSize + Vector2D(-DELTA.x, DELTA.y); eMouseBindMode mode = g_pInputManager->m_dragMode; - if (DRAGGINGWINDOW->m_windowData.keepAspectRatio.valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO) + if (DRAGGINGWINDOW->m_ruleApplicator->keepAspectRatio().valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO) mode = MBIND_RESIZE_FORCE_RATIO; if (m_beginDragSizeXY.x >= 1 && m_beginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) { @@ -803,14 +804,15 @@ void IHyprLayout::changeWindowFloatingMode(PHLWINDOW pWindow) { g_pHyprRenderer->damageMonitor(pWindow->m_monitor.lock()); - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); if (pWindow == m_lastTiledWindow) m_lastTiledWindow.reset(); } - g_pCompositor->updateWindowAnimatedDecorationValues(pWindow); + pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE | Desktop::Rule::RULE_PROP_FLOATING); + pWindow->updateDecorationValues(); pWindow->updateToplevel(); g_pHyprRenderer->damageWindow(pWindow); } @@ -885,7 +887,7 @@ PHLWINDOW IHyprLayout::getNextWindowCandidate(PHLWINDOW pWindow) { // find whether there is a floating window below this one for (auto const& w : g_pCompositor->m_windows) { if (w->m_isMapped && !w->isHidden() && w->m_isFloating && !w->isX11OverrideRedirect() && w->m_workspace == pWindow->m_workspace && !w->m_X11ShouldntFocus && - !w->m_windowData.noFocus.valueOrDefault() && w != pWindow) { + !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pWindow) { if (VECINRECT((pWindow->m_size / 2.f + pWindow->m_position), w->m_position.x, w->m_position.y, w->m_position.x + w->m_size.x, w->m_position.y + w->m_size.y)) { return w; } @@ -904,7 +906,7 @@ PHLWINDOW IHyprLayout::getNextWindowCandidate(PHLWINDOW pWindow) { // if not, floating window for (auto const& w : g_pCompositor->m_windows) { if (w->m_isMapped && !w->isHidden() && w->m_isFloating && !w->isX11OverrideRedirect() && w->m_workspace == pWindow->m_workspace && !w->m_X11ShouldntFocus && - !w->m_windowData.noFocus.valueOrDefault() && w != pWindow) + !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pWindow) return w; } @@ -953,7 +955,7 @@ Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // ge if (g_pCompositor->m_lastMonitor) { // If `persistentsize` is set, use the stored size if available. - const bool HASPERSISTENTSIZE = std::ranges::any_of(pWindow->m_matchedRules, [](const auto& rule) { return rule->m_ruleType == CWindowRule::RULE_PERSISTENTSIZE; }); + const bool HASPERSISTENTSIZE = pWindow->m_ruleApplicator->persistentSize().valueOrDefault(); const auto STOREDSIZE = HASPERSISTENTSIZE ? g_pConfigManager->getStoredFloatingSize(pWindow) : std::nullopt; @@ -962,27 +964,10 @@ Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // ge return STOREDSIZE.value(); } - for (auto const& r : g_pConfigManager->getMatchingRules(pWindow, true, true)) { - if (r->m_ruleType != CWindowRule::RULE_SIZE) - continue; - - try { - const auto VALUE = r->m_rule.substr(r->m_rule.find(' ') + 1); - const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' ')); - const auto SIZEYSTR = VALUE.substr(VALUE.find(' ') + 1); - - const auto MAXSIZE = pWindow->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); - - sizeOverride = {SIZEX, SIZEY}; - - } catch (...) { Debug::log(LOG, "Rule size failed, rule: {} -> {}", r->m_rule, r->m_value); } - break; + if (!pWindow->m_ruleApplicator->static_.size.empty()) { + const auto SIZE = Desktop::Rule::parseRelativeVector(pWindow, pWindow->m_ruleApplicator->static_.size); + if (SIZE) + return SIZE.value(); } } @@ -990,17 +975,7 @@ Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // ge } Vector2D IHyprLayout::predictSizeForNewWindow(PHLWINDOW pWindow) { - bool shouldBeFloated = g_pXWaylandManager->shouldBeFloated(pWindow, true); - - if (!shouldBeFloated) { - for (auto const& r : g_pConfigManager->getMatchingRules(pWindow, true, true)) { - if (r->m_ruleType != CWindowRule::RULE_FLOAT) - continue; - - shouldBeFloated = true; - break; - } - } + bool shouldBeFloated = g_pXWaylandManager->shouldBeFloated(pWindow, true) || pWindow->m_ruleApplicator->static_.floating.value_or(false); Vector2D sizePredicted = {}; @@ -1043,7 +1018,7 @@ bool IHyprLayout::updateDragWindow() { const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); *DRAGGINGWINDOW->m_realPosition = MOUSECOORDS - DRAGGINGWINDOW->m_realSize->goal() / 2.f; } else if (!DRAGGINGWINDOW->m_isFloating && g_pInputManager->m_dragMode == MBIND_MOVE) { - Vector2D MINSIZE = DRAGGINGWINDOW->requestedMinSize().clamp(DRAGGINGWINDOW->m_windowData.minSize.valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE))); + Vector2D MINSIZE = DRAGGINGWINDOW->requestedMinSize().clamp(DRAGGINGWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE))); DRAGGINGWINDOW->m_lastFloatingSize = (DRAGGINGWINDOW->m_realSize->goal() * 0.8489).clamp(MINSIZE, Vector2D{}).floor(); *DRAGGINGWINDOW->m_realPosition = g_pInputManager->getMouseCoordsInternal() - DRAGGINGWINDOW->m_realSize->goal() / 2.f; if (g_pInputManager->m_dragThresholdReached) { diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp index 5dde65d6..5b2284c5 100644 --- a/src/layout/MasterLayout.cpp +++ b/src/layout/MasterLayout.cpp @@ -242,7 +242,7 @@ void CHyprMasterLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { const auto MASTERSLEFT = getMastersOnWorkspace(WORKSPACEID); static auto SMALLSPLIT = CConfigValue("master:allow_small_split"); - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); if (pWindow->isFullscreen()) @@ -663,7 +663,7 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { if (PWINDOW->isFullscreen() && !pNode->ignoreFullscreenChecks) return; - PWINDOW->unsetWindowData(PRIORITY_LAYOUT); + PWINDOW->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); PWINDOW->updateWindowData(); static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); @@ -708,10 +708,10 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { Vector2D monitorAvailable = PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight - Vector2D{(double)(gapsOut.m_left + gapsOut.m_right), (double)(gapsOut.m_top + gapsOut.m_bottom)} - Vector2D{2.0 * borderSize, 2.0 * borderSize}; - Vector2D minSize = PWINDOW->m_windowData.minSize.valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); - Vector2D maxSize = - PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : PWINDOW->m_windowData.maxSize.valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable); - calcSize = calcSize.clamp(minSize, maxSize); + Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); + Vector2D maxSize = PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable); + calcSize = calcSize.clamp(minSize, maxSize); calcPos += (availableSpace - calcSize) / 2.0; @@ -762,9 +762,9 @@ void CHyprMasterLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorne const auto PNODE = getNodeFromWindow(PWINDOW); if (!PNODE) { - *PWINDOW->m_realSize = - (PWINDOW->m_realSize->goal() + pixResize) - .clamp(PWINDOW->m_windowData.minSize.valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), PWINDOW->m_windowData.maxSize.valueOr(Vector2D{INFINITY, INFINITY})); + *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + pixResize) + .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY})); PWINDOW->updateWindowDecos(); return; } @@ -919,7 +919,7 @@ void CHyprMasterLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFul *pWindow->m_realPosition = pWindow->m_lastFloatingPosition; *pWindow->m_realSize = pWindow->m_lastFloatingSize; - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); } } else { diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 8194f739..feee0369 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -21,6 +21,8 @@ #include "../render/Renderer.hpp" #include "../hyprerror/HyprError.hpp" #include "../config/ConfigManager.hpp" +#include "../desktop/rule/windowRule/WindowRule.hpp" +#include "../desktop/rule/Engine.hpp" #include #include @@ -929,18 +931,20 @@ uint64_t CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitial args = args.substr(args.find_first_of(']') + 1); } - const uint64_t PROC = spawnRawProc(args, pInitialWorkspace); + std::string execToken = ""; if (!RULES.empty()) { - const auto RULESLIST = CVarList(RULES, 0, ';'); + auto rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(RULES)); - for (auto const& r : RULESLIST) { - g_pConfigManager->addExecRule({r, sc(PROC)}); - } + execToken = rule->execToken(); - Debug::log(LOG, "Applied {} rule arguments for exec.", RULESLIST.size()); + Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); + + Debug::log(LOG, "Applied rule arguments for exec."); } + const uint64_t PROC = spawnRawProc(args, pInitialWorkspace, execToken); + return PROC; } @@ -949,7 +953,7 @@ SDispatchResult CKeybindManager::spawnRaw(std::string args) { return {.success = PROC > 0, .error = std::format("Failed to start process {}", args)}; } -uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWorkspace) { +uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken) { Debug::log(LOG, "Executing {}", args); const auto HLENV = getHyprlandLaunchEnv(pInitialWorkspace); @@ -971,6 +975,8 @@ uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWo setenv(e.first.c_str(), e.second.c_str(), 1); } setenv("WAYLAND_DISPLAY", g_pCompositor->m_wlDisplaySocket.c_str(), 1); + if (!execRuleToken.empty()) + setenv(Desktop::Rule::EXEC_RULE_ENV_NAME, execRuleToken.c_str(), true); int devnull = open("/dev/null", O_WRONLY | O_CLOEXEC); if (devnull != -1) { @@ -1344,7 +1350,7 @@ SDispatchResult CKeybindManager::fullscreenStateActive(std::string args) { if (!PWINDOW) return {.success = false, .error = "Window not found"}; - PWINDOW->m_windowData.syncFullscreen = CWindowOverridableVar(false, PRIORITY_SET_PROP); + PWINDOW->m_ruleApplicator->syncFullscreenOverride(Desktop::Types::COverridableVar(false, Desktop::Types::PRIORITY_SET_PROP)); int internalMode, clientMode; try { @@ -1370,7 +1376,8 @@ SDispatchResult CKeybindManager::fullscreenStateActive(std::string args) { g_pCompositor->setWindowFullscreenState(PWINDOW, STATE); } - PWINDOW->m_windowData.syncFullscreen = CWindowOverridableVar(PWINDOW->m_fullscreenState.internal == PWINDOW->m_fullscreenState.client, PRIORITY_SET_PROP); + PWINDOW->m_ruleApplicator->syncFullscreenOverride( + Desktop::Types::COverridableVar(PWINDOW->m_fullscreenState.internal == PWINDOW->m_fullscreenState.client, Desktop::Types::PRIORITY_SET_PROP)); return {}; } @@ -2363,9 +2370,9 @@ SDispatchResult CKeybindManager::tagWindow(std::string args) { else return {.success = false, .error = "Invalid number of arguments, expected 1 or 2 arguments"}; - if (PWINDOW && PWINDOW->m_tags.applyTag(vars[0])) { - PWINDOW->updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW->m_self.lock()); + if (PWINDOW && PWINDOW->m_ruleApplicator->m_tagKeeper.applyTag(vars[0])) { + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_TAG); + PWINDOW->updateDecorationValues(); } return {}; @@ -2756,8 +2763,7 @@ SDispatchResult CKeybindManager::pinActive(std::string args) { PWINDOW->m_workspace = PMONITOR->m_activeWorkspace; - PWINDOW->updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_PINNED); const auto PWORKSPACE = PWINDOW->m_workspace; @@ -2887,7 +2893,7 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { else PHEAD->m_groupData.locked = false; - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); return {}; } @@ -3064,7 +3070,7 @@ SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { PWINDOW->warpCursor(); } - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); return {}; } @@ -3092,7 +3098,7 @@ SDispatchResult CKeybindManager::denyWindowFromGroup(std::string args) { else PWINDOW->m_groupData.deny = args == "on"; - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); return {}; } @@ -3142,6 +3148,39 @@ SDispatchResult CKeybindManager::event(std::string args) { #include #include +template +static void parsePropTrivial(Desktop::Types::COverridableVar& prop, const std::string& s) { + static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, + "Invalid type passed to parsePropTrivial"); + + if (s == "unset") { + prop.unset(Desktop::Types::PRIORITY_SET_PROP); + return; + } + + try { + if constexpr (std::is_same_v) { + if (s == "toggle") + prop.increment(true, Desktop::Types::PRIORITY_SET_PROP); + else + prop = Desktop::Types::COverridableVar(truthy(s), Desktop::Types::PRIORITY_SET_PROP); + } else if constexpr (std::is_same_v || std::is_same_v) { + if (s.starts_with("relative")) { + const auto VAL = std::stoi(s.substr(s.find(' ') + 1)); + prop.increment(VAL, Desktop::Types::PRIORITY_SET_PROP); + } else + prop = Desktop::Types::COverridableVar(std::stoull(s), Desktop::Types::PRIORITY_SET_PROP); + } else if constexpr (std::is_same_v) { + if (s.starts_with("relative")) { + const auto VAL = std::stof(s.substr(s.find(' ') + 1)); + prop.increment(VAL, Desktop::Types::PRIORITY_SET_PROP); + } else + prop = Desktop::Types::COverridableVar(std::stof(s), Desktop::Types::PRIORITY_SET_PROP); + } else if constexpr (std::is_same_v) + prop = Desktop::Types::COverridableVar(s, Desktop::Types::PRIORITY_SET_PROP); + } catch (...) { Debug::log(ERR, "Hyprctl: parsePropTrivial: failed to parse setprop for {}", s); } +} + SDispatchResult CKeybindManager::setProp(std::string args) { CVarList vars(args, 3, ' '); @@ -3157,37 +3196,18 @@ SDispatchResult CKeybindManager::setProp(std::string args) { const auto PROP = vars[1]; const auto VAL = vars[2]; - bool noFocus = PWINDOW->m_windowData.noFocus.valueOrDefault(); + bool noFocus = PWINDOW->m_ruleApplicator->noFocus().valueOrDefault(); try { - if (PROP == "animationstyle") { - PWINDOW->m_windowData.animationStyle = CWindowOverridableVar(VAL, PRIORITY_SET_PROP); - } else if (PROP == "maxsize") { - PWINDOW->m_windowData.maxSize = CWindowOverridableVar(configStringToVector2D(VAL), PRIORITY_SET_PROP); - PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_windowData.maxSize.value()); + if (PROP == "max_size") { + PWINDOW->m_ruleApplicator->maxSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); + PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->maxSize().value()); PWINDOW->setHidden(false); - } else if (PROP == "minsize") { - PWINDOW->m_windowData.minSize = CWindowOverridableVar(configStringToVector2D(VAL), PRIORITY_SET_PROP); - PWINDOW->clampWindowSize(PWINDOW->m_windowData.minSize.value(), std::nullopt); + } else if (PROP == "min_size") { + PWINDOW->m_ruleApplicator->minSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); + PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->minSize().value()); PWINDOW->setHidden(false); - } else if (PROP == "alpha") { - PWINDOW->m_windowData.alpha = CWindowOverridableVar(SAlphaValue{std::stof(VAL), PWINDOW->m_windowData.alpha.valueOrDefault().overridden}, PRIORITY_SET_PROP); - } else if (PROP == "alphainactive") { - PWINDOW->m_windowData.alphaInactive = - CWindowOverridableVar(SAlphaValue{std::stof(VAL), PWINDOW->m_windowData.alphaInactive.valueOrDefault().overridden}, PRIORITY_SET_PROP); - } else if (PROP == "alphafullscreen") { - PWINDOW->m_windowData.alphaFullscreen = - CWindowOverridableVar(SAlphaValue{std::stof(VAL), PWINDOW->m_windowData.alphaFullscreen.valueOrDefault().overridden}, PRIORITY_SET_PROP); - } else if (PROP == "alphaoverride") { - PWINDOW->m_windowData.alpha = - CWindowOverridableVar(SAlphaValue{PWINDOW->m_windowData.alpha.valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, PRIORITY_SET_PROP); - } else if (PROP == "alphainactiveoverride") { - PWINDOW->m_windowData.alphaInactive = - CWindowOverridableVar(SAlphaValue{PWINDOW->m_windowData.alphaInactive.valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, PRIORITY_SET_PROP); - } else if (PROP == "alphafullscreenoverride") { - PWINDOW->m_windowData.alphaFullscreen = - CWindowOverridableVar(SAlphaValue{PWINDOW->m_windowData.alphaFullscreen.valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, PRIORITY_SET_PROP); - } else if (PROP == "activebordercolor" || PROP == "inactivebordercolor") { + } else if (PROP == "active_border_color" || PROP == "inactive_border_color") { CGradientValueData colorData = {}; if (vars.size() > 4) { for (int i = 3; i < sc(vars.size()); ++i) { @@ -3208,43 +3228,101 @@ SDispatchResult CKeybindManager::setProp(std::string args) { colorData.updateColorsOk(); - if (PROP == "activebordercolor") - PWINDOW->m_windowData.activeBorderColor = CWindowOverridableVar(colorData, PRIORITY_SET_PROP); + if (PROP == "active_border_color") + PWINDOW->m_ruleApplicator->activeBorderColorOverride(Desktop::Types::COverridableVar(colorData, Desktop::Types::PRIORITY_SET_PROP)); else - PWINDOW->m_windowData.inactiveBorderColor = CWindowOverridableVar(colorData, PRIORITY_SET_PROP); - } else if (auto search = NWindowProperties::boolWindowProperties.find(PROP); search != NWindowProperties::boolWindowProperties.end()) { - auto pWindowDataElement = search->second(PWINDOW); - if (VAL == "toggle") - pWindowDataElement->increment(true, PRIORITY_SET_PROP); - else if (VAL == "unset") - pWindowDataElement->unset(PRIORITY_SET_PROP); - else - *pWindowDataElement = CWindowOverridableVar(sc(configStringToInt(VAL).value_or(0)), PRIORITY_SET_PROP); - } else if (auto search = NWindowProperties::intWindowProperties.find(PROP); search != NWindowProperties::intWindowProperties.end()) { - if (VAL == "unset") - search->second(PWINDOW)->unset(PRIORITY_SET_PROP); - else if (VAL.starts_with("relative")) { - const Hyprlang::INT V = std::stoi(VAL.substr(VAL.find(' '))); - search->second(PWINDOW)->increment(V, PRIORITY_SET_PROP); - } else if (const auto V = configStringToInt(VAL); V) - *(search->second(PWINDOW)) = CWindowOverridableVar(*V, PRIORITY_SET_PROP); - } else if (auto search = NWindowProperties::floatWindowProperties.find(PROP); search != NWindowProperties::floatWindowProperties.end()) { - if (VAL == "unset") - search->second(PWINDOW)->unset(PRIORITY_SET_PROP); - else if (VAL.starts_with("relative")) { - const auto V = std::stof(VAL.substr(VAL.find(' '))); - search->second(PWINDOW)->increment(V, PRIORITY_SET_PROP); - } else { - const auto V = std::stof(VAL); - *(search->second(PWINDOW)) = CWindowOverridableVar(V, PRIORITY_SET_PROP); - } - } else - return {.success = false, .error = "Prop not found"}; + PWINDOW->m_ruleApplicator->inactiveBorderColorOverride(Desktop::Types::COverridableVar(colorData, Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity") { + PWINDOW->m_ruleApplicator->alphaOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alpha().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_inactive") { + PWINDOW->m_ruleApplicator->alphaInactiveOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alphaInactive().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_fullscreen") { + PWINDOW->m_ruleApplicator->alphaFullscreenOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alphaFullscreen().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_override") { + PWINDOW->m_ruleApplicator->alphaOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alpha().valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, + Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_inactive_override") { + PWINDOW->m_ruleApplicator->alphaInactiveOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaInactive().valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, + Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_fullscreen_override") { + PWINDOW->m_ruleApplicator->alphaFullscreenOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaFullscreen().valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, + Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "allows_input") + parsePropTrivial(PWINDOW->m_ruleApplicator->allowsInput(), VAL); + else if (PROP == "decorate") + parsePropTrivial(PWINDOW->m_ruleApplicator->decorate(), VAL); + else if (PROP == "focus_on_activate") + parsePropTrivial(PWINDOW->m_ruleApplicator->focusOnActivate(), VAL); + else if (PROP == "keep_aspect_ratio") + parsePropTrivial(PWINDOW->m_ruleApplicator->keepAspectRatio(), VAL); + else if (PROP == "nearest_neighbor") + parsePropTrivial(PWINDOW->m_ruleApplicator->nearestNeighbor(), VAL); + else if (PROP == "no_anim") + parsePropTrivial(PWINDOW->m_ruleApplicator->noAnim(), VAL); + else if (PROP == "no_blur") + parsePropTrivial(PWINDOW->m_ruleApplicator->noBlur(), VAL); + else if (PROP == "no_dim") + parsePropTrivial(PWINDOW->m_ruleApplicator->noDim(), VAL); + else if (PROP == "no_focus") + parsePropTrivial(PWINDOW->m_ruleApplicator->noFocus(), VAL); + else if (PROP == "no_max_size") + parsePropTrivial(PWINDOW->m_ruleApplicator->noMaxSize(), VAL); + else if (PROP == "no_shadow") + parsePropTrivial(PWINDOW->m_ruleApplicator->noShadow(), VAL); + else if (PROP == "no_shortcuts_inhibit") + parsePropTrivial(PWINDOW->m_ruleApplicator->noShortcutsInhibit(), VAL); + else if (PROP == "dim_around") + parsePropTrivial(PWINDOW->m_ruleApplicator->dimAround(), VAL); + else if (PROP == "opaque") + parsePropTrivial(PWINDOW->m_ruleApplicator->opaque(), VAL); + else if (PROP == "force_rgbx") + parsePropTrivial(PWINDOW->m_ruleApplicator->RGBX(), VAL); + else if (PROP == "sync_fullscreen") + parsePropTrivial(PWINDOW->m_ruleApplicator->syncFullscreen(), VAL); + else if (PROP == "immediate") + parsePropTrivial(PWINDOW->m_ruleApplicator->tearing(), VAL); + else if (PROP == "xray") + parsePropTrivial(PWINDOW->m_ruleApplicator->xray(), VAL); + else if (PROP == "render_unfocused") + parsePropTrivial(PWINDOW->m_ruleApplicator->renderUnfocused(), VAL); + else if (PROP == "no_follow_mouse") + parsePropTrivial(PWINDOW->m_ruleApplicator->noFollowMouse(), VAL); + else if (PROP == "no_screen_share") + parsePropTrivial(PWINDOW->m_ruleApplicator->noScreenShare(), VAL); + else if (PROP == "no_vrr") + parsePropTrivial(PWINDOW->m_ruleApplicator->noVRR(), VAL); + else if (PROP == "persistent_size") + parsePropTrivial(PWINDOW->m_ruleApplicator->persistentSize(), VAL); + else if (PROP == "stay_focused") + parsePropTrivial(PWINDOW->m_ruleApplicator->stayFocused(), VAL); + else if (PROP == "idle_inhibit") + parsePropTrivial(PWINDOW->m_ruleApplicator->idleInhibitMode(), VAL); + else if (PROP == "border_size") + parsePropTrivial(PWINDOW->m_ruleApplicator->borderSize(), VAL); + else if (PROP == "rounding") + parsePropTrivial(PWINDOW->m_ruleApplicator->rounding(), VAL); + else if (PROP == "rounding_power") + parsePropTrivial(PWINDOW->m_ruleApplicator->roundingPower(), VAL); + else if (PROP == "scroll_mouse") + parsePropTrivial(PWINDOW->m_ruleApplicator->scrollMouse(), VAL); + else if (PROP == "scroll_touchpad") + parsePropTrivial(PWINDOW->m_ruleApplicator->scrollTouchpad(), VAL); + else if (PROP == "animation") + parsePropTrivial(PWINDOW->m_ruleApplicator->animationStyle(), VAL); + else + return {.success = false, .error = "prop not found"}; + } catch (std::exception& e) { return {.success = false, .error = std::format("Error parsing prop value: {}", std::string(e.what()))}; } g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - if (!(PWINDOW->m_windowData.noFocus.valueOrDefault() == noFocus)) { + if (!(PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() == noFocus)) { g_pCompositor->focusWindow(nullptr); g_pCompositor->focusWindow(PWINDOW); g_pCompositor->focusWindow(PLASTWINDOW); diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index e3433a10..b4100beb 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -164,7 +164,7 @@ class CKeybindManager { static void moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir = ""); static void moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection); static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool preserveFocusHistory = false); - static uint64_t spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace); + static uint64_t spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken = ""); static uint64_t spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); // -------------- Dispatchers -------------- // diff --git a/src/managers/animation/AnimationManager.cpp b/src/managers/animation/AnimationManager.cpp index f38f4ccf..38efb829 100644 --- a/src/managers/animation/AnimationManager.cpp +++ b/src/managers/animation/AnimationManager.cpp @@ -98,7 +98,7 @@ static void handleUpdate(CAnimatedVariable& av, bool warp) { if (!PMONITOR) return; - animationsDisabled = PWINDOW->m_windowData.noAnim.valueOr(animationsDisabled); + animationsDisabled = PWINDOW->m_ruleApplicator->noAnim().valueOr(animationsDisabled); } else if (PWORKSPACE) { PMONITOR = PWORKSPACE->m_monitor.lock(); if (!PMONITOR) @@ -142,7 +142,7 @@ static void handleUpdate(CAnimatedVariable& av, bool warp) { PMONITOR = g_pCompositor->getMonitorFromVector(PLAYER->m_realPosition->goal() + PLAYER->m_realSize->goal() / 2.F); if (!PMONITOR) return; - animationsDisabled = animationsDisabled || PLAYER->m_noAnimations; + animationsDisabled = animationsDisabled || PLAYER->m_ruleApplicator->noanim().valueOrDefault(); } const auto SPENT = av.getPercent(); diff --git a/src/managers/animation/DesktopAnimationManager.cpp b/src/managers/animation/DesktopAnimationManager.cpp index f156dfa9..8a340554 100644 --- a/src/managers/animation/DesktopAnimationManager.cpp +++ b/src/managers/animation/DesktopAnimationManager.cpp @@ -41,8 +41,8 @@ void CDesktopAnimationManager::startAnimation(PHLWINDOW pWindow, eAnimationType if (!pWindow->m_realPosition->enabled() && !force) return; - if (pWindow->m_windowData.animationStyle.hasValue()) { - const auto STYLE = pWindow->m_windowData.animationStyle.value(); + if (pWindow->m_ruleApplicator->animationStyle().hasValue()) { + const auto STYLE = pWindow->m_ruleApplicator->animationStyle().value(); // the window has config'd special anim if (STYLE.starts_with("slide")) { CVarList animList2(STYLE, 0, 's'); @@ -106,7 +106,7 @@ void CDesktopAnimationManager::startAnimation(PHLLS ls, eAnimationType type, boo ls->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeLayersOut")); } - const auto ANIMSTYLE = ls->m_animationStyle.value_or(ls->m_realPosition->getStyle()); + const auto ANIMSTYLE = ls->m_ruleApplicator->animationStyle().valueOr(ls->m_realPosition->getStyle()); if (ANIMSTYLE.starts_with("slide")) { // get closest edge const auto MIDDLE = ls->m_geometry.middle(); diff --git a/src/managers/input/IdleInhibitor.cpp b/src/managers/input/IdleInhibitor.cpp index 82f43f47..851e917a 100644 --- a/src/managers/input/IdleInhibitor.cpp +++ b/src/managers/input/IdleInhibitor.cpp @@ -61,13 +61,13 @@ void CInputManager::recheckIdleInhibitorStatus() { } bool CInputManager::isWindowInhibiting(const PHLWINDOW& w, bool onlyHl) { - if (w->m_idleInhibitMode == IDLEINHIBIT_ALWAYS) + if (w->m_ruleApplicator->idleInhibitMode().valueOrDefault() == Desktop::Rule::IDLEINHIBIT_ALWAYS) return true; - if (w->m_idleInhibitMode == IDLEINHIBIT_FOCUS && g_pCompositor->isWindowActive(w)) + if (w->m_ruleApplicator->idleInhibitMode().valueOrDefault() == Desktop::Rule::IDLEINHIBIT_FOCUS && g_pCompositor->isWindowActive(w)) return true; - if (w->m_idleInhibitMode == IDLEINHIBIT_FULLSCREEN && w->isFullscreen() && w->m_workspace && w->m_workspace->isVisible()) + if (w->m_ruleApplicator->idleInhibitMode().valueOrDefault() == Desktop::Rule::IDLEINHIBIT_FULLSCREEN && w->isFullscreen() && w->m_workspace && w->m_workspace->isVisible()) return true; if (onlyHl) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index f44f2101..d1d8ec15 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -585,7 +585,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st // Temp fix until that's figured out. Otherwise spams windowrule lookups and other shit. if (m_lastMouseFocus.lock() != pFoundWindow || g_pCompositor->m_lastWindow.lock() != pFoundWindow) { if (m_mousePosDelta > *PFOLLOWMOUSETHRESHOLD || refocus) { - const bool hasNoFollowMouse = pFoundWindow && pFoundWindow->m_windowData.noFollowMouse.valueOrDefault(); + const bool hasNoFollowMouse = pFoundWindow && pFoundWindow->m_ruleApplicator->noFollowMouse().valueOrDefault(); if (refocus || !hasNoFollowMouse) g_pCompositor->focusWindow(pFoundWindow, foundSurface); diff --git a/src/managers/input/trackpad/gestures/CloseGesture.cpp b/src/managers/input/trackpad/gestures/CloseGesture.cpp index 59beb712..ad2d0f45 100644 --- a/src/managers/input/trackpad/gestures/CloseGesture.cpp +++ b/src/managers/input/trackpad/gestures/CloseGesture.cpp @@ -133,7 +133,7 @@ void CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) return; g_pLayoutManager->getCurrentLayout()->recalculateWindow(window.lock()); - g_pCompositor->updateWindowAnimatedDecorationValues(window.lock()); + window->updateDecorationValues(); window->sendWindowSize(true); *window->m_alpha = 1.F; }, diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp index e2a32c91..84487a18 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -230,7 +230,7 @@ void CScreencopyFrame::renderMon() { }; for (auto const& l : g_pCompositor->m_layers) { - if (!l->m_noScreenShare) + if (!l->m_ruleApplicator->noScreenShare().valueOrDefault()) continue; if UNLIKELY ((!l->m_mapped && !l->m_fadingOut) || l->m_alpha->value() == 0.f) @@ -251,7 +251,7 @@ void CScreencopyFrame::renderMon() { } for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_windowData.noScreenShare.valueOrDefault()) + if (!w->m_ruleApplicator->noScreenShare().valueOrDefault()) continue; if (!g_pHyprRenderer->shouldRenderWindow(w, m_monitor.lock())) @@ -272,7 +272,7 @@ void CScreencopyFrame::renderMon() { .scale(m_monitor->m_scale) .translate(-m_box.pos()); - const auto dontRound = w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN) || w->m_windowData.noRounding.valueOrDefault(); + const auto dontRound = w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); const auto rounding = dontRound ? 0 : w->rounding() * m_monitor->m_scale; const auto roundingPower = dontRound ? 2.0f : w->roundingPower(); diff --git a/src/protocols/ShortcutsInhibit.cpp b/src/protocols/ShortcutsInhibit.cpp index b33db998..749390cd 100644 --- a/src/protocols/ShortcutsInhibit.cpp +++ b/src/protocols/ShortcutsInhibit.cpp @@ -70,7 +70,7 @@ bool CKeyboardShortcutsInhibitProtocol::isInhibited() { if (!g_pCompositor->m_lastFocus) return false; - if (const auto PWINDOW = g_pCompositor->getWindowFromSurface(g_pCompositor->m_lastFocus.lock()); PWINDOW && PWINDOW->m_windowData.noShortcutsInhibit.valueOrDefault()) + if (const auto PWINDOW = g_pCompositor->getWindowFromSurface(g_pCompositor->m_lastFocus.lock()); PWINDOW && PWINDOW->m_ruleApplicator->noShortcutsInhibit().valueOrDefault()) return false; for (auto const& in : m_inhibitors) { diff --git a/src/protocols/ToplevelExport.cpp b/src/protocols/ToplevelExport.cpp index eb0a39aa..c66c1f2b 100644 --- a/src/protocols/ToplevelExport.cpp +++ b/src/protocols/ToplevelExport.cpp @@ -257,7 +257,7 @@ bool CToplevelExportFrame::copyShm(const Time::steady_tp& now) { // render client at 0,0 if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_windowData.noScreenShare.valueOrDefault()) { + if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); g_pHyprRenderer->m_bBlockSurfaceFeedback = false; @@ -339,7 +339,7 @@ bool CToplevelExportFrame::copyDmabuf(const Time::steady_tp& now) { g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 1.0)); if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_windowData.noScreenShare.valueOrDefault()) { + if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); g_pHyprRenderer->m_bBlockSurfaceFeedback = false; diff --git a/src/protocols/XDGDialog.cpp b/src/protocols/XDGDialog.cpp index c38a1077..c64a8379 100644 --- a/src/protocols/XDGDialog.cpp +++ b/src/protocols/XDGDialog.cpp @@ -30,7 +30,7 @@ void CXDGDialogV1Resource::updateWindow() { if UNLIKELY (!HLSurface || !HLSurface->getWindow()) return; - g_pCompositor->updateWindowAnimatedDecorationValues(HLSurface->getWindow()); + HLSurface->getWindow()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_MODAL); } bool CXDGDialogV1Resource::good() { diff --git a/src/protocols/XDGTag.cpp b/src/protocols/XDGTag.cpp index 2966ac90..98c8651f 100644 --- a/src/protocols/XDGTag.cpp +++ b/src/protocols/XDGTag.cpp @@ -1,5 +1,6 @@ #include "XDGTag.hpp" #include "XDGShell.hpp" +#include "../desktop/Window.hpp" CXDGToplevelTagManagerResource::CXDGToplevelTagManagerResource(UP&& resource) : m_resource(std::move(resource)) { if UNLIKELY (!good()) @@ -17,6 +18,8 @@ CXDGToplevelTagManagerResource::CXDGToplevelTagManagerResource(UPm_toplevelTag = tag; + if (TOPLEVEL->m_window) + TOPLEVEL->m_window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_XDG_TAG); }); m_resource->setSetToplevelDescription([](CXdgToplevelTagManagerV1* r, wl_resource* toplevel, const char* description) { diff --git a/src/protocols/types/ContentType.cpp b/src/protocols/types/ContentType.cpp index c0a3d30f..b5b0041c 100644 --- a/src/protocols/types/ContentType.cpp +++ b/src/protocols/types/ContentType.cpp @@ -12,7 +12,15 @@ namespace NContentType { if (it != table.end()) return it->second; else - throw std::invalid_argument(std::format("Unknown content type {}", name)); + return CONTENT_TYPE_NONE; + } + + std::string toString(eContentType type) { + for (const auto& [k, v] : table) { + if (v == type) + return k; + } + return ""; } eContentType fromWP(wpContentTypeV1Type contentType) { diff --git a/src/protocols/types/ContentType.hpp b/src/protocols/types/ContentType.hpp index 66fcbca7..68bc7a41 100644 --- a/src/protocols/types/ContentType.hpp +++ b/src/protocols/types/ContentType.hpp @@ -13,6 +13,7 @@ namespace NContentType { }; eContentType fromString(const std::string name); + std::string toString(eContentType); eContentType fromWP(wpContentTypeV1Type contentType); uint16_t toDRM(eContentType contentType); } \ No newline at end of file diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 548b2f8b..b2ec69f3 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1670,7 +1670,7 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c } } - if (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.RGBX.valueOrDefault()) { + if (m_renderData.currentWindow && m_renderData.currentWindow->m_ruleApplicator->RGBX().valueOrDefault()) { shader = &m_shaders->m_shRGBX; texType = TEXTURE_RGBX; } @@ -2195,7 +2195,7 @@ void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) { if (!pWindow) return false; - if (pWindow->m_windowData.noBlur.valueOrDefault()) + if (pWindow->m_ruleApplicator->noBlur().valueOrDefault()) return false; if (pWindow->m_wlSurface->small() && !pWindow->m_wlSurface->m_fillIgnoreSmall) @@ -2239,7 +2239,7 @@ void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) { for (auto const& m : g_pCompositor->m_monitors) { for (auto const& lsl : m->m_layerSurfaceLayers) { for (auto const& ls : lsl) { - if (!ls->m_layerSurface || ls->m_xray != 1) + if (!ls->m_layerSurface || ls->m_ruleApplicator->xray().valueOrDefault() != 1) continue; // if (ls->layerSurface->surface->opaque && ls->alpha->value() >= 1.f) @@ -2311,16 +2311,16 @@ bool CHyprOpenGLImpl::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWin if (!m_renderData.pCurrentMonData->blurFB.getTexture()) return false; - if (pWindow && pWindow->m_windowData.xray.hasValue() && !pWindow->m_windowData.xray.valueOrDefault()) + if (pWindow && pWindow->m_ruleApplicator->xray().hasValue() && !pWindow->m_ruleApplicator->xray().valueOrDefault()) return false; - if (pLayer && pLayer->m_xray == 0) + if (pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 0) return false; if ((*PBLURNEWOPTIMIZE && pWindow && !pWindow->m_isFloating && !pWindow->onSpecialWorkspace()) || *PBLURXRAY) return true; - if ((pLayer && pLayer->m_xray == 1) || (pWindow && pWindow->m_windowData.xray.valueOrDefault())) + if ((pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 1) || (pWindow && pWindow->m_ruleApplicator->xray().valueOrDefault())) return true; return false; @@ -2474,7 +2474,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr TRACY_GPU_ZONE("RenderBorder"); - if (m_renderData.damage.empty() || (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.noBorder.valueOrDefault())) + if (m_renderData.damage.empty()) return; CBox newBox = box; @@ -2558,7 +2558,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr TRACY_GPU_ZONE("RenderBorder2"); - if (m_renderData.damage.empty() || (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.noBorder.valueOrDefault())) + if (m_renderData.damage.empty()) return; CBox newBox = box; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index b115bab9..aec2bbc6 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -167,7 +167,7 @@ CHyprRenderer::CHyprRenderer() { } if (dirty) - std::erase_if(m_renderUnfocused, [](const auto& e) { return !e || !e->m_windowData.renderUnfocused.valueOr(false); }); + std::erase_if(m_renderUnfocused, [](const auto& e) { return !e || !e->m_ruleApplicator->renderUnfocused().valueOr(false); }); if (!m_renderUnfocused.empty()) m_renderUnfocusedTimer->updateTimeout(std::chrono::milliseconds(1000 / *PFPS)); @@ -509,7 +509,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T const bool USE_WORKSPACE_FADE_ALPHA = pWindow->m_monitorMovedFrom != -1 && (!PWORKSPACE || !PWORKSPACE->isVisible()); renderdata.surface = pWindow->m_wlSurface->resource(); - renderdata.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN) || pWindow->m_windowData.noRounding.valueOrDefault(); + renderdata.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); renderdata.fadeAlpha = pWindow->m_alpha->value() * (pWindow->m_pinned || USE_WORKSPACE_FADE_ALPHA ? 1.f : PWORKSPACE->m_alpha->value()) * (USE_WORKSPACE_FADE_ALPHA ? pWindow->m_movingToWorkspaceAlpha->value() : 1.F) * pWindow->m_movingFromWorkspaceAlpha->value(); renderdata.alpha = pWindow->m_activeInactiveAlpha->value(); @@ -525,7 +525,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } // apply opaque - if (pWindow->m_windowData.opaque.valueOrDefault()) + if (pWindow->m_ruleApplicator->opaque().valueOrDefault()) renderdata.alpha = 1.f; renderdata.pWindow = pWindow; @@ -537,7 +537,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T const auto fullAlpha = renderdata.alpha * renderdata.fadeAlpha; - if (*PDIMAROUND && pWindow->m_windowData.dimAround.valueOrDefault() && !m_bRenderingSnapshot && mode != RENDER_PASS_POPUP) { + if (*PDIMAROUND && pWindow->m_ruleApplicator->dimAround().valueOrDefault() && !m_bRenderingSnapshot && mode != RENDER_PASS_POPUP) { CBox monbox = {0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.y}; CRectPassElement::SRectData data; data.color = CHyprColor(0, 0, 0, *PDIMAROUND * fullAlpha); @@ -585,7 +585,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } static auto PXWLUSENN = CConfigValue("xwayland:use_nearest_neighbor"); - if ((pWindow->m_isX11 && *PXWLUSENN) || pWindow->m_windowData.nearestNeighbor.valueOrDefault()) + if ((pWindow->m_isX11 && *PXWLUSENN) || pWindow->m_ruleApplicator->nearestNeighbor().valueOrDefault()) renderdata.useNearestNeighbor = true; if (pWindow->m_wlSurface->small() && !pWindow->m_wlSurface->m_fillIgnoreSmall && renderdata.blur) { @@ -657,7 +657,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T renderdata.discardOpacity = *PBLURIGNOREA; } - if (pWindow->m_windowData.nearestNeighbor.valueOrDefault()) + if (pWindow->m_ruleApplicator->nearestNeighbor().valueOrDefault()) renderdata.useNearestNeighbor = true; renderdata.surfaceCounter = 0; @@ -714,12 +714,13 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s return; // skip rendering based on abovelock rule and make sure to not render abovelock layers twice - if ((pLayer->m_aboveLockscreen && !lockscreen && g_pSessionLockManager->isSessionLocked()) || (lockscreen && !pLayer->m_aboveLockscreen)) + if ((pLayer->m_ruleApplicator->aboveLock().valueOrDefault() && !lockscreen && g_pSessionLockManager->isSessionLocked()) || + (lockscreen && !pLayer->m_ruleApplicator->aboveLock().valueOrDefault())) return; static auto PDIMAROUND = CConfigValue("decoration:dim_around"); - if (*PDIMAROUND && pLayer->m_dimAround && !m_bRenderingSnapshot && !popups) { + if (*PDIMAROUND && pLayer->m_ruleApplicator->dimAround().valueOrDefault() && !m_bRenderingSnapshot && !popups) { CRectPassElement::SRectData data; data.box = {0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.y}; data.color = CHyprColor(0, 0, 0, *PDIMAROUND * pLayer->m_alpha->value()); @@ -749,9 +750,9 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s renderdata.clipBox = CBox{0, 0, pMonitor->m_size.x, pMonitor->m_size.y}.scale(pMonitor->m_scale); - if (renderdata.blur && pLayer->m_ignoreAlpha) { + if (renderdata.blur && pLayer->m_ruleApplicator->ignoreAlpha().hasValue()) { renderdata.discardMode |= DISCARD_ALPHA; - renderdata.discardOpacity = pLayer->m_ignoreAlphaValue; + renderdata.discardOpacity = pLayer->m_ruleApplicator->ignoreAlpha().valueOrDefault(); } if (!popups) @@ -769,7 +770,7 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s renderdata.squishOversized = false; // don't squish popups renderdata.dontRound = true; renderdata.popup = true; - renderdata.blur = pLayer->m_forceBlurPopups; + renderdata.blur = pLayer->m_ruleApplicator->blurPopups().valueOrDefault(); renderdata.surfaceCounter = 0; if (popups) { pLayer->m_popupHead->breadthfirst( @@ -1859,7 +1860,8 @@ void CHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) { } for (auto& la : PMONITOR->m_layerSurfaceLayers) { - std::ranges::stable_sort(la, [](const PHLLSREF& a, const PHLLSREF& b) { return a->m_order > b->m_order; }); + std::ranges::stable_sort( + la, [](const PHLLSREF& a, const PHLLSREF& b) { return a->m_ruleApplicator->order().valueOrDefault() > b->m_ruleApplicator->order().valueOrDefault(); }); } for (auto const& la : PMONITOR->m_layerSurfaceLayers) @@ -2564,7 +2566,7 @@ void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y}; - if (*PDIMAROUND && pWindow->m_windowData.dimAround.valueOrDefault()) { + if (*PDIMAROUND && pWindow->m_ruleApplicator->dimAround().valueOrDefault()) { CRectPassElement::SRectData data; data.box = {0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.y}; @@ -2581,7 +2583,7 @@ void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { data.blurA = sqrt(pWindow->m_alpha->value()); // sqrt makes the blur fadeout more realistic. data.round = pWindow->rounding(); data.roundingPower = pWindow->roundingPower(); - data.xray = pWindow->m_windowData.xray.valueOr(false); + data.xray = pWindow->m_ruleApplicator->xray().valueOr(false); m_renderPass.add(makeUnique(std::move(data))); } @@ -2633,7 +2635,7 @@ void CHyprRenderer::renderSnapshot(PHLLS pLayer) { data.blur = SHOULD_BLUR; data.blurA = sqrt(pLayer->m_alpha->value()); // sqrt makes the blur fadeout more realistic. if (SHOULD_BLUR) - data.ignoreAlpha = pLayer->m_ignoreAlpha ? pLayer->m_ignoreAlphaValue : 0.01F /* ignore the alpha 0 regions */; + data.ignoreAlpha = pLayer->m_ruleApplicator->ignoreAlpha().valueOr(0.01F) /* ignore the alpha 0 regions */; m_renderPass.add(makeUnique(std::move(data))); } @@ -2678,7 +2680,7 @@ bool CHyprRenderer::shouldBlur(PHLLS ls) { return false; static auto PBLUR = CConfigValue("decoration:blur:enabled"); - return *PBLUR && ls->m_forceBlur; + return *PBLUR && ls->m_ruleApplicator->blur().valueOrDefault(); } bool CHyprRenderer::shouldBlur(PHLWINDOW w) { @@ -2686,7 +2688,7 @@ bool CHyprRenderer::shouldBlur(PHLWINDOW w) { return false; static auto PBLUR = CConfigValue("decoration:blur:enabled"); - const bool DONT_BLUR = w->m_windowData.noBlur.valueOrDefault() || w->m_windowData.RGBX.valueOrDefault() || w->opaque(); + const bool DONT_BLUR = w->m_ruleApplicator->noBlur().valueOrDefault() || w->m_ruleApplicator->RGBX().valueOrDefault() || w->opaque(); return *PBLUR && !DONT_BLUR; } diff --git a/src/render/decorations/CHyprBorderDecoration.cpp b/src/render/decorations/CHyprBorderDecoration.cpp index 75298ff7..a082f073 100644 --- a/src/render/decorations/CHyprBorderDecoration.cpp +++ b/src/render/decorations/CHyprBorderDecoration.cpp @@ -161,6 +161,5 @@ std::string CHyprBorderDecoration::getDisplayName() { } bool CHyprBorderDecoration::doesntWantBorders() { - return m_window->m_windowData.noBorder.valueOrDefault() || m_window->m_X11DoesntWantBorders || m_window->getRealBorderSize() == 0 || - !m_window->m_windowData.decorate.valueOrDefault(); + return m_window->m_X11DoesntWantBorders || m_window->getRealBorderSize() == 0 || !m_window->m_ruleApplicator->decorate().valueOrDefault(); } diff --git a/src/render/decorations/CHyprDropShadowDecoration.cpp b/src/render/decorations/CHyprDropShadowDecoration.cpp index bcc4c84e..dd82abc5 100644 --- a/src/render/decorations/CHyprDropShadowDecoration.cpp +++ b/src/render/decorations/CHyprDropShadowDecoration.cpp @@ -104,10 +104,10 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { if (PWINDOW->m_realShadowColor->value() == CHyprColor(0, 0, 0, 0)) return; // don't draw invisible shadows - if (!PWINDOW->m_windowData.decorate.valueOrDefault()) + if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault()) return; - if (PWINDOW->m_windowData.noShadow.valueOrDefault()) + if (PWINDOW->m_ruleApplicator->noShadow().valueOrDefault()) return; static auto PSHADOWS = CConfigValue("decoration:shadow:enabled"); diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp index dbf66b60..3b95d749 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -597,5 +597,5 @@ CBox CHyprGroupBarDecoration::assignedBoxGlobal() { bool CHyprGroupBarDecoration::visible() { static auto PENABLED = CConfigValue("group:groupbar:enabled"); - return *PENABLED && m_window->m_windowData.decorate.valueOrDefault(); + return *PENABLED && m_window->m_ruleApplicator->decorate().valueOrDefault(); }