From b77cbad50251f0506b61d834b025247dcf74dddf Mon Sep 17 00:00:00 2001 From: Vaxry Date: Wed, 12 Nov 2025 22:43:46 +0000 Subject: [PATCH 01/56] screencopy: fix possible crash in renderMon() --- src/protocols/Screencopy.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp index 1b47fb66e..e2a32c91b 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -246,7 +246,8 @@ void CScreencopyFrame::renderMon() { const auto geom = l->m_geometry; const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; - l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); + if (l->m_popupHead) + l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); } for (auto const& w : g_pCompositor->m_windows) { From 64ee8f8a72d62069a6bef45ca05bef1d0d412e1f Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Thu, 13 Nov 2025 00:08:04 +0000 Subject: [PATCH 02/56] layout: include reserved area in float fit (#12289) Ref https://github.com/basecamp/omarchy/issues/3327 --- hyprtester/src/tests/main/dwindle.cpp | 3 ++- src/helpers/Monitor.cpp | 4 ++++ src/helpers/Monitor.hpp | 1 + src/layout/IHyprLayout.cpp | 21 +++++++++++---------- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/hyprtester/src/tests/main/dwindle.cpp b/hyprtester/src/tests/main/dwindle.cpp index a75646c8d..8f17c8152 100644 --- a/hyprtester/src/tests/main/dwindle.cpp +++ b/hyprtester/src/tests/main/dwindle.cpp @@ -16,6 +16,7 @@ static void testFloatClamp() { } OK(getFromSocket("/keyword dwindle:force_split 2")); + OK(getFromSocket("/keyword monitor HEADLESS-2, addreserved, 0, 20, 0, 20")); OK(getFromSocket("/dispatch focuswindow class:c")); OK(getFromSocket("/dispatch setfloating class:c")); OK(getFromSocket("/dispatch resizewindowpixel exact 1200 900,class:c")); @@ -24,7 +25,7 @@ static void testFloatClamp() { { auto str = getFromSocket("/clients"); - EXPECT_CONTAINS(str, "at: 718,178"); + EXPECT_CONTAINS(str, "at: 698,158"); EXPECT_CONTAINS(str, "size: 1200,900"); } diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 74dd995b0..50a8c1204 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -1509,6 +1509,10 @@ CBox CMonitor::logicalBox() { return {m_position, m_size}; } +CBox CMonitor::logicalBoxMinusExtents() { + return {m_position + m_reservedTopLeft, m_size - m_reservedTopLeft - m_reservedBottomRight}; +} + void CMonitor::scheduleDone() { if (m_doneScheduled) return; diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 5be6a7ebc..f1f466698 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -298,6 +298,7 @@ class CMonitor { WORKSPACEID activeWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID(); CBox logicalBox(); + CBox logicalBoxMinusExtents(); void scheduleDone(); uint32_t isSolitaryBlocked(bool full = false); void recheckSolitary(); diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index 388684244..702d6ac92 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -826,19 +826,20 @@ void IHyprLayout::fitFloatingWindowOnMonitor(PHLWINDOW w, std::optional tb const auto EXTENTS = w->getWindowExtentsUnified(RESERVED_EXTENTS | INPUT_EXTENTS); CBox targetBoxMonLocal = tb.value_or(w->getWindowMainSurfaceBox()).translate(-PMONITOR->m_position).addExtents(EXTENTS); + const auto MONITOR_LOCAL_BOX = PMONITOR->logicalBoxMinusExtents().translate(-PMONITOR->m_position); - if (targetBoxMonLocal.w < PMONITOR->m_size.x) { - if (targetBoxMonLocal.x < 0) - targetBoxMonLocal.x = 0; - else if (targetBoxMonLocal.x + targetBoxMonLocal.w > PMONITOR->m_size.x) - targetBoxMonLocal.x = PMONITOR->m_size.x - targetBoxMonLocal.w; + if (targetBoxMonLocal.w < MONITOR_LOCAL_BOX.w) { + if (targetBoxMonLocal.x < MONITOR_LOCAL_BOX.x) + targetBoxMonLocal.x = MONITOR_LOCAL_BOX.x; + else if (targetBoxMonLocal.x + targetBoxMonLocal.w > MONITOR_LOCAL_BOX.w) + targetBoxMonLocal.x = MONITOR_LOCAL_BOX.w - targetBoxMonLocal.w; } - if (targetBoxMonLocal.h < PMONITOR->m_size.y) { - if (targetBoxMonLocal.y < 0) - targetBoxMonLocal.y = 0; - else if (targetBoxMonLocal.y + targetBoxMonLocal.h > PMONITOR->m_size.y) - targetBoxMonLocal.y = PMONITOR->m_size.y - targetBoxMonLocal.h; + if (targetBoxMonLocal.h < MONITOR_LOCAL_BOX.h) { + if (targetBoxMonLocal.y < MONITOR_LOCAL_BOX.y) + targetBoxMonLocal.y = MONITOR_LOCAL_BOX.y; + else if (targetBoxMonLocal.y + targetBoxMonLocal.h > MONITOR_LOCAL_BOX.h) + targetBoxMonLocal.y = MONITOR_LOCAL_BOX.h - targetBoxMonLocal.h; } *w->m_realPosition = (targetBoxMonLocal.pos() + PMONITOR->m_position + EXTENTS.topLeft).round(); From 55a93b8a52ad3bd2c0a7468e8f79f5667eea0702 Mon Sep 17 00:00:00 2001 From: Hiroki Tagato Date: Fri, 14 Nov 2025 07:06:25 +0900 Subject: [PATCH 03/56] internal: put Linux-only header behind ifdef (#12300) --- src/config/ConfigWatcher.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config/ConfigWatcher.cpp b/src/config/ConfigWatcher.cpp index f3a8a2cad..dfd84b43b 100644 --- a/src/config/ConfigWatcher.cpp +++ b/src/config/ConfigWatcher.cpp @@ -1,5 +1,7 @@ #include "ConfigWatcher.hpp" +#if defined(__linux__) #include +#endif #include #include "../debug/Log.hpp" #include From 43527d363472b52f17dd9f9f4f87ec25cbf8a399 Mon Sep 17 00:00:00 2001 From: Hiroki Tagato Date: Fri, 14 Nov 2025 07:06:34 +0900 Subject: [PATCH 04/56] internal: fix crash at startup on FreeBSD (#12298) Hyprland at the latest commit crashes at starting up on FreeBSD with SIGSEGV. Checking the validity of g_pXWayland->m_wm before calling updateWorkArea() appears to fix the issue. --- src/Compositor.cpp | 2 ++ src/layout/DwindleLayout.cpp | 2 ++ src/layout/MasterLayout.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 2dc798e47..702ed8966 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -3029,6 +3029,8 @@ void CCompositor::arrangeMonitors() { #ifndef NO_XWAYLAND CBox box = g_pCompositor->calculateX11WorkArea(); + if (!g_pXWayland || !g_pXWayland->m_wm) + return; g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); #endif } diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp index e46a09633..6df544457 100644 --- a/src/layout/DwindleLayout.cpp +++ b/src/layout/DwindleLayout.cpp @@ -601,6 +601,8 @@ void CHyprDwindleLayout::recalculateMonitor(const MONITORID& monid) { #ifndef NO_XWAYLAND CBox box = g_pCompositor->calculateX11WorkArea(); + if (!g_pXWayland || !g_pXWayland->m_wm) + return; g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); #endif } diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp index b40c339ac..5dde65d6d 100644 --- a/src/layout/MasterLayout.cpp +++ b/src/layout/MasterLayout.cpp @@ -297,6 +297,8 @@ void CHyprMasterLayout::recalculateMonitor(const MONITORID& monid) { #ifndef NO_XWAYLAND CBox box = g_pCompositor->calculateX11WorkArea(); + if (!g_pXWayland || !g_pXWayland->m_wm) + return; g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); #endif } From b62ab4b5786d55f7f2d70a71410f83f8ce91af6a Mon Sep 17 00:00:00 2001 From: Lucas Ritzdorf <42657792+LRitzdorf@users.noreply.github.com> Date: Sat, 15 Nov 2025 11:23:19 -0800 Subject: [PATCH 05/56] cmake,meson: fix inclusion of GPG info in Git commit info (#12302) This manifested for me as a failure to build plugins with `hyprpm`, but the root cause was GPG data getting incorporated into `src/version.h`, like so: ```c #define GIT_COMMIT_MESSAGE "gpg: Signature made Sun 09 Nov 2025 03:31:36 PM PST gpg: using EDDSA key E26A4A2AB9676F54149F8EAA665806380871D640 gpg: Can't check signature: No public key version: bump to 0.52.1" ``` This affected both `GIT_COMMIT_MESSAGE` and `GIT_COMMIT_DATE`, since those are generated via `git show` (which can generate that extra GPG info if the user's personal Git config sets `log.showSignature`). See: https://github.com/hyprwm/Hyprland/discussions/12282 --- CMakeLists.txt | 4 ++-- meson.build | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 29de4a324..91443da6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,10 +155,10 @@ if(Git_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} branch --show-current WORKING_DIRECTORY ${GIT_TOPLEVEL} OUTPUT_VARIABLE GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%s + execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%s --no-show-signature WORKING_DIRECTORY ${GIT_TOPLEVEL} OUTPUT_VARIABLE GIT_COMMIT_MESSAGE OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%cd --date=local + execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%cd --date=local --no-show-signature WORKING_DIRECTORY ${GIT_TOPLEVEL} OUTPUT_VARIABLE GIT_COMMIT_DATE OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${GIT_EXECUTABLE} diff-index --quiet HEAD -- diff --git a/meson.build b/meson.build index c7819e80b..e1e2df949 100644 --- a/meson.build +++ b/meson.build @@ -46,8 +46,8 @@ git = find_program('git', required: false) if git.found() git_hash = run_command(git, 'rev-parse', 'HEAD').stdout().strip() git_branch = run_command(git, 'branch', '--show-current').stdout().strip() - git_message = run_command(git, 'show', '-s', '--format=%s').stdout().strip() - git_date = run_command(git, 'show', '-s', '--format=%cd', '--date=local').stdout().strip() + git_message = run_command(git, 'show', '-s', '--format=%s', '--no-show-signature').stdout().strip() + git_date = run_command(git, 'show', '-s', '--format=%cd', '--date=local', '--no-show-signature').stdout().strip() git_dirty = run_command(git, 'diff-index', '--quiet', 'HEAD', '--', check: false).returncode() != 0 ? 'dirty' : 'clean' git_tag = run_command(git, 'describe', '--tags').stdout().strip() git_commits = run_command(git, 'rev-list', '--count', 'HEAD').stdout().strip() From b35f78431f5a8cec1df1ff8595b239fcb0ba3e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20Sp=C3=AEnu?= Date: Sat, 15 Nov 2025 21:23:32 +0200 Subject: [PATCH 06/56] cursor: ensure cursor reset on changed window states (#12301) --- src/managers/input/InputManager.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index fe93a83bb..1f1b0d0d0 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -546,6 +546,13 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st if (*PRESIZEONBORDER && *PRESIZECURSORICON) { if (!pFoundWindow->isFullscreen() && !pFoundWindow->hasPopupAt(mouseCoords)) setCursorIconOnBorder(pFoundWindow); + else if (m_borderIconDirection != BORDERICON_NONE) { + m_borderIconDirection = BORDERICON_NONE; + Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); + } + } else if (m_borderIconDirection != BORDERICON_NONE) { + m_borderIconDirection = BORDERICON_NONE; + Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); } if (FOLLOWMOUSE != 1 && !refocus) { From 9b006b2c8533bae1e528bd123be209453787b9b7 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Sun, 16 Nov 2025 12:01:48 +0000 Subject: [PATCH 07/56] plugin/hook: disallow multiple hooks per function (#12320) this was never safe. After recent changes, it's become even less so. Just disallow it. ref #11992 --- src/plugins/HookSystem.cpp | 10 ++++++++++ src/plugins/HookSystem.hpp | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp index c5def6a5f..031b1def6 100644 --- a/src/plugins/HookSystem.cpp +++ b/src/plugins/HookSystem.cpp @@ -144,6 +144,12 @@ bool CFunctionHook::hook() { return false; #endif + if (g_pFunctionHookSystem->m_activeHooks.contains(rc(m_source))) { + // TODO: return actual error codes... + Debug::log(ERR, "[functionhook] failed, function is already hooked"); + return false; + } + // jmp rel32 // offset for relative addr: 1 static constexpr uint8_t RELATIVE_JMP_ADDRESS[] = {0xE9, 0x00, 0x00, 0x00, 0x00}; @@ -231,6 +237,8 @@ bool CFunctionHook::hook() { m_active = true; m_hookLen = ORIGSIZE; + g_pFunctionHookSystem->m_activeHooks.emplace(rc(m_source)); + return true; } @@ -243,6 +251,8 @@ bool CFunctionHook::unhook() { if (!m_active) return false; + g_pFunctionHookSystem->m_activeHooks.erase(rc(m_source)); + // allow write to src mprotect(sc(m_source) - rc(m_source) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC); diff --git a/src/plugins/HookSystem.hpp b/src/plugins/HookSystem.hpp index cf098dd1b..3431e8c8f 100644 --- a/src/plugins/HookSystem.hpp +++ b/src/plugins/HookSystem.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../helpers/memory/Memory.hpp" #define HANDLE void* @@ -70,7 +71,8 @@ class CHookSystem { uint64_t used = 0; }; - std::vector m_pages; + std::vector m_pages; + std::unordered_set m_activeHooks; friend class CFunctionHook; }; From cb47eb1d11bbac1ebab8f9cd7a5f96a475eaab28 Mon Sep 17 00:00:00 2001 From: Jochim Date: Sun, 16 Nov 2025 13:23:45 +0100 Subject: [PATCH 08/56] deco/groupbar: add groupbar blur (#12310) --- src/config/ConfigDescriptions.hpp | 6 ++ src/config/ConfigManager.cpp | 1 + .../decorations/CHyprGroupBarDecoration.cpp | 61 ++++++++----------- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 926d49ac7..5e5034518 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1115,6 +1115,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SRangeData{0, -20, 20}, }, + SConfigOptionDescription{ + .value = "group:groupbar:blur", + .description = "enable background blur for groupbars", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, /* * misc: diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index a3c60d2bb..b4b6ed3de 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -560,6 +560,7 @@ CConfigManager::CConfigManager() { registerConfigVar("group:groupbar:gaps_in", Hyprlang::INT{2}); registerConfigVar("group:groupbar:keep_upper_gap", Hyprlang::INT{1}); registerConfigVar("group:groupbar:text_offset", Hyprlang::INT{0}); + registerConfigVar("group:groupbar:blur", Hyprlang::INT{0}); registerConfigVar("debug:log_damage", Hyprlang::INT{0}); registerConfigVar("debug:overlay", Hyprlang::INT{0}); diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp index 49fcd3471..b4bf2b86a 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -126,6 +126,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { static auto PINNERGAP = CConfigValue("group:groupbar:gaps_in"); static auto PKEEPUPPERGAP = CConfigValue("group:groupbar:keep_upper_gap"); static auto PTEXTOFFSET = CConfigValue("group:groupbar:text_offset"); + static auto PBLUR = CConfigValue("group:groupbar:blur"); auto* const GROUPCOLACTIVE = sc((PGROUPCOLACTIVE.ptr())->getData()); auto* const GROUPCOLINACTIVE = sc((PGROUPCOLINACTIVE.ptr())->getData()); auto* const GROUPCOLACTIVELOCKED = sc((PGROUPCOLACTIVELOCKED.ptr())->getData()); @@ -144,6 +145,8 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { float xoff = 0; float yoff = 0; + bool blur = *PBLUR != 0; + for (int i = 0; i < barsToDraw; ++i) { const auto WINDOWINDEX = *PSTACKED ? m_dwGroupMembers.size() - i - 1 : i; @@ -163,31 +166,23 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { if (!rect.empty()) { CRectPassElement::SRectData rectdata; rectdata.color = color; + rectdata.blur = blur; rectdata.box = rect; if (*PROUNDING) { + rectdata.round = *PROUNDING; rectdata.roundingPower = *PROUNDINGPOWER; if (*PROUNDONLYEDGES) { - static constexpr double PADDING = 20; - - if (i == 0 && barsToDraw == 1) - rectdata.round = *PROUNDING; - else if (i == 0) { - double first = rect.w - (*PROUNDING * 2); + if (i == 0) { rectdata.round = *PROUNDING; - rectdata.clipBox = CBox{rect.pos() - Vector2D{PADDING, 0.F}, Vector2D{first + PADDING, rect.h}}; - g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); - rectdata.round = 0; - rectdata.clipBox = CBox{rect.pos() + Vector2D{first, 0.F}, Vector2D{rect.w - first + PADDING, rect.h}}; + rectdata.clipBox = rect; + rectdata.box = CBox{rect.pos(), Vector2D{rect.w + (*PROUNDING * 2), rect.h}}; } else if (i == barsToDraw - 1) { - double first = *PROUNDING * 2; - rectdata.round = 0; - rectdata.clipBox = CBox{rect.pos() - Vector2D{PADDING, 0.F}, Vector2D{first + PADDING, rect.h}}; - g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); + double offset = *PROUNDING * 2; rectdata.round = *PROUNDING; - rectdata.clipBox = CBox{rect.pos() + Vector2D{first, 0.F}, Vector2D{rect.w - first + PADDING, rect.h}}; + rectdata.clipBox = rect; + rectdata.box = CBox{rect.pos() - Vector2D{offset, 0.F}, Vector2D{rect.w + offset, rect.h}}; } - } else - rectdata.round = *PROUNDING; + } } g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); } @@ -203,32 +198,24 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { (GROUPLOCKED ? m_tGradientLockedInactive : m_tGradientInactive)); if (GRADIENTTEX->m_texID) { CTexPassElement::SRenderData data; - data.tex = GRADIENTTEX; - data.box = rect; + data.tex = GRADIENTTEX; + data.blur = blur; + data.box = rect; if (*PGRADIENTROUNDING) { + data.round = *PGRADIENTROUNDING; data.roundingPower = *PGRADIENTROUNDINGPOWER; if (*PGRADIENTROUNDINGONLYEDGES) { - static constexpr double PADDING = 20; - - if (i == 0 && barsToDraw == 1) - data.round = *PGRADIENTROUNDING; - else if (i == 0) { - double first = rect.w - (*PGRADIENTROUNDING * 2); + if (i == 0) { data.round = *PGRADIENTROUNDING; - data.clipBox = CBox{rect.pos() - Vector2D{PADDING, 0.F}, Vector2D{first + PADDING, rect.h}}; - g_pHyprRenderer->m_renderPass.add(makeUnique(data)); - data.round = 0; - data.clipBox = CBox{rect.pos() + Vector2D{first, 0.F}, Vector2D{rect.w - first + PADDING, rect.h}}; + data.clipBox = rect; + data.box = CBox{rect.pos(), Vector2D{rect.w + (*PGRADIENTROUNDING * 2), rect.h}}; } else if (i == barsToDraw - 1) { - double first = *PGRADIENTROUNDING * 2; - data.round = 0; - data.clipBox = CBox{rect.pos() - Vector2D{PADDING, 0.F}, Vector2D{first + PADDING, rect.h}}; - g_pHyprRenderer->m_renderPass.add(makeUnique(data)); - data.round = *PGRADIENTROUNDING; - data.clipBox = CBox{rect.pos() + Vector2D{first, 0.F}, Vector2D{rect.w - first + PADDING, rect.h}}; + double offset = *PGRADIENTROUNDING * 2; + data.round = *PGRADIENTROUNDING; + data.clipBox = rect; + data.box = CBox{rect.pos() - Vector2D{offset, 0.F}, Vector2D{rect.w + offset, rect.h}}; } - } else - data.round = *PGRADIENTROUNDING; + } } g_pHyprRenderer->m_renderPass.add(makeUnique(data)); } From e616e595ae5c1af89a336e4e337a41b9855eaf6e Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Sun, 16 Nov 2025 14:51:14 +0000 Subject: [PATCH 09/56] i18n: init localization for ANR, Permissions and Notifications (#12316) Adds localization support for en, it, pl and jp --------- Co-authored-by: Mihai Fufezan Co-authored-by: Aaron Blasko --- CMakeLists.txt | 2 +- flake.lock | 6 +- src/Compositor.cpp | 18 +- src/helpers/Monitor.cpp | 11 +- src/i18n/Engine.cpp | 175 ++++++++++++++++++ src/i18n/Engine.hpp | 50 +++++ src/managers/ANRManager.cpp | 32 +++- src/managers/ANRManager.hpp | 2 +- .../permissions/DynamicPermissionManager.cpp | 62 +++---- src/plugins/PluginSystem.cpp | 4 +- src/render/OpenGL.cpp | 3 +- src/render/Renderer.cpp | 4 +- 12 files changed, 302 insertions(+), 67 deletions(-) create mode 100644 src/i18n/Engine.cpp create mode 100644 src/i18n/Engine.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 91443da6a..e7c97b7b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,7 +108,7 @@ find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.9.3) pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2) pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7) -pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.8.2) +pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.10.2) pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.6) string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION}) diff --git a/flake.lock b/flake.lock index 221c4558d..b9fb17867 100644 --- a/flake.lock +++ b/flake.lock @@ -261,11 +261,11 @@ ] }, "locked": { - "lastModified": 1762387740, - "narHash": "sha256-gQ9zJ+pUI4o+Gh4Z6jhJll7jjCSwi8ZqJIhCE2oqwhQ=", + "lastModified": 1762812168, + "narHash": "sha256-pY+dUqi2AYpH0HHT2JFzt1qWoJQBWtBdzzcL1ZK5Mwo=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "926689ddb9c0a8787e58c02c765a62e32d63d1f7", + "rev": "cb3e797fde5c748164eb70d9859336141136a166", "type": "github" }, "original": { diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 702ed8966..a7f26ba6d 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -65,6 +65,7 @@ #include "debug/HyprNotificationOverlay.hpp" #include "debug/HyprDebugOverlay.hpp" #include "helpers/MonitorFrameScheduler.hpp" +#include "i18n/Engine.hpp" #include #include @@ -2765,22 +2766,19 @@ void CCompositor::performUserChecks() { const auto CURRENT_DESKTOP_ENV = getenv("XDG_CURRENT_DESKTOP"); if (!CURRENT_DESKTOP_ENV || std::string{CURRENT_DESKTOP_ENV} != "Hyprland") { g_pHyprNotificationOverlay->addNotification( - std::format("Your XDG_CURRENT_DESKTOP environment seems to be managed externally, and the current value is {}.\nThis might cause issues unless it's intentional.", - CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : "unset"), - CHyprColor{}, 15000, ICON_WARNING); + I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, {{"value", CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : "unset"}}), CHyprColor{}, 15000, + ICON_WARNING); } } if (!*PNOCHECKGUIUTILS) { if (!NFsUtils::executableExistsInPath("hyprland-dialog")) { - g_pHyprNotificationOverlay->addNotification( - "Your system does not have hyprland-guiutils installed. This is a runtime dependency for some dialogs. Consider installing it.", CHyprColor{}, 15000, ICON_WARNING); + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_GUIUTILS), CHyprColor{}, 15000, ICON_WARNING); } } if (g_pHyprOpenGL->m_failedAssetsNo > 0) { - g_pHyprNotificationOverlay->addNotification(std::format("Hyprland failed to load {} essential asset{}, blame your distro's packager for doing a bad job at packaging!", - g_pHyprOpenGL->m_failedAssetsNo, g_pHyprOpenGL->m_failedAssetsNo > 1 ? "s" : ""), + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_ASSETS, {{"count", std::to_string(g_pHyprOpenGL->m_failedAssetsNo)}}), CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_ERROR); } } @@ -2905,10 +2903,8 @@ void CCompositor::checkMonitorOverlaps() { for (const auto& m : m_monitors) { if (!monitorRegion.copy().intersect(m->logicalBox()).empty()) { Debug::log(ERR, "Monitor {}: detected overlap with layout", m->m_name); - g_pHyprNotificationOverlay->addNotification(std::format("Your monitor layout is set up incorrectly. Monitor {} overlaps with other monitor(s) in the " - "layout.\nPlease see the wiki (Monitors page) for more. This will cause issues.", - m->m_name), - CHyprColor{}, 15000, ICON_WARNING); + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, {{"name", m->m_name}}), CHyprColor{}, 15000, + ICON_WARNING); break; } diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 50a8c1204..1a7b4ac64 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -27,6 +27,7 @@ #include "../managers/animation/DesktopAnimationManager.hpp" #include "../managers/input/InputManager.hpp" #include "../hyprerror/HyprError.hpp" +#include "../i18n/Engine.hpp" #include "sync/SyncTimeline.hpp" #include "time/Time.hpp" #include "../desktop/LayerSurface.hpp" @@ -811,8 +812,8 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { if (!m_state.test()) continue; - auto errorMessage = - std::format("Monitor {} failed to set any requested modes, falling back to mode {:X0}@{:.2f}Hz", m_name, mode->pixelSize, mode->refreshRate / 1000.f); + auto errorMessage = I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + {{"name", m_name}, {"mode", std::format("{:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f)}}); Debug::log(WARN, errorMessage); g_pHyprNotificationOverlay->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING); @@ -939,8 +940,10 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { Debug::log(ERR, "Invalid scale passed to monitor, {} found suggestion {}", m_scale, searchScale); static auto PDISABLENOTIFICATION = CConfigValue("misc:disable_scale_notification"); if (!*PDISABLENOTIFICATION) - g_pHyprNotificationOverlay->addNotification(std::format("Invalid scale passed to monitor: {}, using suggested scale: {}", m_scale, searchScale), - CHyprColor(1.0, 0.0, 0.0, 1.0), 5000, ICON_WARNING); + g_pHyprNotificationOverlay->addNotification( + I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + {{"name", m_name}, {"scale", std::format("{:.2f}", m_scale)}, {"fixed_scale", std::format("{:.2f}", searchScale)}}), + CHyprColor(1.0, 0.0, 0.0, 1.0), 5000, ICON_WARNING); } m_scale = searchScale; } diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp new file mode 100644 index 000000000..716d3fc31 --- /dev/null +++ b/src/i18n/Engine.cpp @@ -0,0 +1,175 @@ +#include "Engine.hpp" + +#include + +using namespace I18n; +using namespace Hyprutils::I18n; + +static SP huEngine; +static std::string localeStr; + +// +SP I18n::i18nEngine() { + static SP engine = makeShared(); + return engine; +} + +I18n::CI18nEngine::CI18nEngine() { + huEngine = makeShared(); + huEngine->setFallbackLocale("en_US"); + localeStr = huEngine->getSystemLocale().locale(); + + // en_US (English) + huEngine->registerEntry("en_US", TXT_KEY_ANR_TITLE, "Application Not Responding"); + huEngine->registerEntry("en_US", TXT_KEY_ANR_CONTENT, "An application {title} - {class} is not responding.\nWhat do you want to do with it?"); + huEngine->registerEntry("en_US", TXT_KEY_ANR_OPTION_TERMINATE, "Terminate"); + huEngine->registerEntry("en_US", TXT_KEY_ANR_OPTION_WAIT, "Wait"); + huEngine->registerEntry("en_US", TXT_KEY_ANR_PROP_UNKNOWN, "(unknown)"); + + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "An application {app} is requesting an unknown permission."); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "An application {app} is trying to capture your screen.\n\nDo you want to allow it to?"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "An application {app} is trying to load a plugin: {plugin}.\n\nDo you want to allow it to?"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "A new keyboard has been detected: {keyboard}.\n\nDo you want to allow it to operate?"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(unknown)"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_TITLE, "Permission request"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Hint: you can set persistent rules for these in the Hyprland config file."); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_ALLOW, "Allow"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Allow and remember"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_ALLOW_ONCE, "Allow once"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_DENY, "Deny"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Unknown application (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "en_US", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Your XDG_CURRENT_DESKTOP environment seems to be managed externally, and the current value is {value}.\nThis might cause issues unless it's intentional."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_NO_GUIUTILS, + "Your system does not have hyprland-guiutils installed. This is a runtime dependency for some dialogs. Consider installing it."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland failed to load {count} essential asset, blame your distro's packager for doing a bad job at packaging!"; + return "Hyprland failed to load {count} essential assets, blame your distro's packager for doing a bad job at packaging!"; + }); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Your monitor layout is set up incorrectly. Monitor {name} overlaps with other monitor(s) in the layout.\nPlease see the wiki (Monitors page) for " + "more. This will cause issues."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} failed to set any requested modes, falling back to mode {mode}."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Invalid scale passed to monitor {name}: {scale}, using suggested scale: {fixed_scale}"); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Failed to load plugin {name}: {error}"); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader reload failed, falling back to rgba/rgbx."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: wide color gamut is enabled but the display is not in 10-bit mode."); + + // it_IT (Italian) + huEngine->registerEntry("it_IT", TXT_KEY_ANR_TITLE, "L'applicazione non risponde"); + huEngine->registerEntry("it_IT", TXT_KEY_ANR_CONTENT, "Un'applicazione {title} - {class} non risponde.\nCosa vuoi fare?"); + huEngine->registerEntry("it_IT", TXT_KEY_ANR_OPTION_TERMINATE, "Termina"); + huEngine->registerEntry("it_IT", TXT_KEY_ANR_OPTION_WAIT, "Attendi"); + huEngine->registerEntry("it_IT", TXT_KEY_ANR_PROP_UNKNOWN, "(sconosciuto)"); + + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Un'applicazione {app} richiede un'autorizzazione sconosciuta."); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Un'applicazione {app} sta provando a catturare il tuo schermo.\n\nGlie lo vuoi permettere?"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "Un'applicazione {app} sta provando a caricare un plugin: {plugin}.\n\nGlie lo vuoi permettere?"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "È stata rilevata una nuova tastiera: {keyboard}.\n\nLe vuoi permettere di operare?"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(sconosciuto)"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_TITLE, "Richiesta di autorizzazione"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Consiglio: Puoi impostare una regola persistente nel tuo file di configurazione di Hyprland."); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_ALLOW, "Permetti"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Permetti e ricorda"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_ALLOW_ONCE, "Permetti una volta"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_DENY, "Nega"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Applicazione sconosciuta (wayland client ID {wayland_id})"); + + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "L'ambiente XDG_CURRENT_DESKTOP sembra essere gestito esternamente, il valore attuale è {value}.\nSe non è voluto, potrebbe causare problemi."); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_NO_GUIUTILS, + "Sembra che hyprland-guiutils non sia installato. È una dipendenza richiesta per alcuni dialoghi che potresti voler installare."); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_FAILED_ASSETS, + "Hyprland non ha potuto caricare {count} asset, dai la colpa al packager della tua distribuzione per il suo cattivo lavoro!"); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "I tuoi schermi sono configurati incorrettamente. Lo schermo {name} si sovrappone con altri nel layout.\nConsulta la wiki (voce Schermi) per " + "altre informazioni. Questo causerà problemi."); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Lo schermo {name} non ha potuto impostare alcuna modalità richiesta, sarà usata la modalità {mode}."); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Fattore di scala non valido per lo schermo {name}: {scale}, utilizzando il fattore suggerito: {fixed_scale}"); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Impossibile caricare il plugin {name}: {error}"); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Impossibile ricaricare gli shader CM, sarà usato rgba/rgbx."); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Schermo {name}: la gamma di colori ampia è abilitata ma lo schermo non è in modalità 10-bit."); + + // ja_JP (Japanese) + huEngine->registerEntry("ja_JP", TXT_KEY_ANR_TITLE, "アプリは応答しません"); + huEngine->registerEntry("ja_JP", TXT_KEY_ANR_CONTENT, "アプリ {title} ー {class}は応答しません。\n何をしたいですか?"); + huEngine->registerEntry("ja_JP", TXT_KEY_ANR_OPTION_TERMINATE, "強制終了"); + huEngine->registerEntry("ja_JP", TXT_KEY_ANR_OPTION_WAIT, "待機"); + huEngine->registerEntry("ja_JP", TXT_KEY_ANR_PROP_UNKNOWN, "(不明)"); + + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "アプリ{app}は不明な許可を要求します。"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "アプリ{app}は画面へのアクセスを要求します。\n\n許可したいですか?"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "アプリ{app}は以下のプラグインをロード許可を要求します:{plugin}。\n\n許可したいですか?"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "新しいキーボードを見つけた:{keyboard}。\n\n稼働を許可したいですか?"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(不明)"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_TITLE, "許可要求"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "ヒント:Hyprlandのコンフィグで通常の許可や却下を設定できます。"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW, "許可"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "保存して許可"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW_ONCE, "一度許可"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_DENY, "却下"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "不明なアプリ (waylandクライアントID {wayland_id})"); + + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "エンバイアロンメント変数「XDG_CURRENT_DESKTOP」は外部から「{value}」に設定しました。\n意図的ではなければ、問題は発生可能性があります。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_NO_GUIUTILS, "システムにhyprland-guiutilsはインストールしていません。このパッケージをインストールしてください。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_ASSETS, + "{count}つの根本的なアセットをロードできませんでした。これはパッケージャーのせいだから、パッケージャーに文句してください。"); + huEngine->registerEntry( + "ja_JP", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "画面の位置設定は誤用です。画面{name}は他の画面の区域と重ね合わせます。\nウィキのモニターページで詳細を確認してください。これは絶対に問題になります。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "画面{name}は設定したモードを正常に受け入れませんでした。{mode}を使いました。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "画面{name}のスケールは無効:{scale}、代わりにおすすめのスケール{fixed_scale}を使いました。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "プラグイン{name}のロード失敗: {error}"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CMシェーダーのリロード失敗、rgba/rgbxを使いました。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "画面{name}:広い色域は設定していますけど、画面は10ビットモードに設定されていません。"); + + // pl_PL (Polish) + huEngine->registerEntry("pl_PL", TXT_KEY_ANR_TITLE, "Aplikacja Nie Odpowiada"); + huEngine->registerEntry("pl_PL", TXT_KEY_ANR_CONTENT, "Aplikacja {title} - {class} nie odpowiada.\nCo chcesz z nią zrobić?"); + huEngine->registerEntry("pl_PL", TXT_KEY_ANR_OPTION_TERMINATE, "Zakończ proces"); + huEngine->registerEntry("pl_PL", TXT_KEY_ANR_OPTION_WAIT, "Czekaj"); + huEngine->registerEntry("pl_PL", TXT_KEY_ANR_PROP_UNKNOWN, "(nieznane)"); + + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Aplikacja {app} prosi o pozwolenie na nieznany typ operacji."); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Aplikacja {app} prosi o dostęp do twojego ekranu.\n\nCzy chcesz jej na to pozwolić?"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Aplikacja {app} próbuje załadować plugin: {plugin}.\n\nCzy chcesz jej na to pozwolić?"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Wykryto nową klawiaturę: {keyboard}.\n\nCzy chcesz jej pozwolić operować?"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(nieznane)"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_TITLE, "Prośba o pozwolenie"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Podpowiedź: możesz ustawić stałe zasady w konfiguracji Hyprland'a."); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_ALLOW, "Zezwól"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Zezwól i zapamiętaj"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_ALLOW_ONCE, "Zezwól raz"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_DENY, "Odmów"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Nieznana aplikacja (ID klienta wayland {wayland_id})"); + + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Zmienna środowiska XDG_CURRENT_DESKTOP została ustawiona zewnętrznie na {value}.\nTo może sprawić problemy, chyba, że jest celowe."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_NO_GUIUTILS, "Twój system nie ma hyprland-guiutils zainstalowanych, co może sprawić problemy. Zainstaluj pakiet."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo == 1) + return "Nie udało się załadować {count} kluczowego zasobu, wiń swojego packager'a za robienie słabej roboty!"; + + return "Nie udało się załadować {count} kluczowych zasobów, wiń swojego packager'a za robienie słabej roboty!"; + }); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Pozycje twoich monitorów nie są ustawione poprawnie. Monitor {name} wchodzi na inne monitory.\nWejdź na wiki (stronę Monitory) " + "po więcej. To będzie sprawiać problemy."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} nie zaakceptował żadnego wybranego programu. Użyto {mode}."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Nieprawidłowa skala dla monitora {name}: {scale}, użyto proponowanej skali: {fixed_scale}"); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nie udało się załadować plugin'a {name}: {error}"); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Nie udało się przeładować shader'a CM, użyto rgba/rgbx."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: skonfigurowano szeroką głębię barw, ale monitor nie jest w trybie 10-bit."); +} + +std::string I18n::CI18nEngine::localize(eI18nKeys key, const Hyprutils::I18n::translationVarMap& vars) { + return huEngine->localizeEntry(localeStr, key, vars); +} diff --git a/src/i18n/Engine.hpp b/src/i18n/Engine.hpp new file mode 100644 index 000000000..d1182632a --- /dev/null +++ b/src/i18n/Engine.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "../helpers/memory/Memory.hpp" +#include +#include +#include + +namespace I18n { + + enum eI18nKeys : uint8_t { + TXT_KEY_ANR_TITLE = 0, + TXT_KEY_ANR_CONTENT, + TXT_KEY_ANR_OPTION_TERMINATE, + TXT_KEY_ANR_OPTION_WAIT, + TXT_KEY_ANR_PROP_UNKNOWN, + + TXT_KEY_PERMISSION_REQUEST_UNKNOWN, + TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, + TXT_KEY_PERMISSION_REQUEST_PLUGIN, + TXT_KEY_PERMISSION_REQUEST_KEYBOARD, + TXT_KEY_PERMISSION_UNKNOWN_NAME, + TXT_KEY_PERMISSION_TITLE, + TXT_KEY_PERMISSION_PERSISTENCE_HINT, + TXT_KEY_PERMISSION_ALLOW, + TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, + TXT_KEY_PERMISSION_ALLOW_ONCE, + TXT_KEY_PERMISSION_DENY, + TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, + + TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + TXT_KEY_NOTIF_NO_GUIUTILS, + TXT_KEY_NOTIF_FAILED_ASSETS, + TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, + TXT_KEY_NOTIF_CM_RELOAD_FAILED, + TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, + }; + + class CI18nEngine { + public: + CI18nEngine(); + ~CI18nEngine() = default; + + std::string localize(eI18nKeys key, const std::unordered_map& vars = {}); + }; + + SP i18nEngine(); +}; \ No newline at end of file diff --git a/src/managers/ANRManager.cpp b/src/managers/ANRManager.cpp index 65e0dea13..daab4d0a9 100644 --- a/src/managers/ANRManager.cpp +++ b/src/managers/ANRManager.cpp @@ -8,6 +8,7 @@ #include "./eventLoop/EventLoopManager.hpp" #include "../config/ConfigValue.hpp" #include "../xwayland/XSurface.hpp" +#include "../i18n/Engine.hpp" using namespace Hyprutils::OS; @@ -83,7 +84,7 @@ void CANRManager::onTick() { if (data->missedResponses >= *PANRTHRESHOLD) { if (!data->isRunning() && !data->dialogSaidWait) { - data->runDialog("Application Not Responding", firstWindow->m_title, firstWindow->m_class, data->getPid()); + data->runDialog(firstWindow->m_title, firstWindow->m_class, data->getPid()); for (const auto& w : g_pCompositor->m_windows) { if (!w->m_isMapped) @@ -176,16 +177,29 @@ CANRManager::SANRData::~SANRData() { killDialog(); } -void CANRManager::SANRData::runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID) { +void CANRManager::SANRData::runDialog(const std::string& appName, const std::string appClass, pid_t dialogWmPID) { if (dialogBox && dialogBox->isRunning()) killDialog(); - dialogBox = CAsyncDialogBox::create(title, - std::format("Application {} with class of {} is not responding.\nWhat do you want to do with it?", appName.empty() ? "unknown" : appName, - appClass.empty() ? "unknown" : appClass), - std::vector{"Terminate", "Wait"}); + const auto OPTION_TERMINATE_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_TERMINATE, {}); + const auto OPTION_WAIT_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_WAIT, {}); - dialogBox->open()->then([dialogWmPID, this](SP> r) { + dialogBox = + CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}), + I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT, + { + // + {"class", appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass}, // + {"title", appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName} // + }), + std::vector{ + // + OPTION_TERMINATE_STR, // + OPTION_WAIT_STR // + } // + ); + + dialogBox->open()->then([dialogWmPID, this, OPTION_TERMINATE_STR, OPTION_WAIT_STR](SP> r) { if (r->hasError()) { Debug::log(ERR, "CANRManager::SANRData::runDialog: error spawning dialog"); return; @@ -193,9 +207,9 @@ void CANRManager::SANRData::runDialog(const std::string& title, const std::strin const auto& result = r->result(); - if (result.starts_with("Terminate")) + if (result.starts_with(OPTION_TERMINATE_STR)) ::kill(dialogWmPID, SIGKILL); - else if (result.starts_with("Wait")) + else if (result.starts_with(OPTION_WAIT_STR)) dialogSaidWait = true; else Debug::log(ERR, "CANRManager::SANRData::runDialog: lambda: unrecognized result: {}", result); diff --git a/src/managers/ANRManager.hpp b/src/managers/ANRManager.hpp index f5c0085ba..286e834f9 100644 --- a/src/managers/ANRManager.hpp +++ b/src/managers/ANRManager.hpp @@ -39,7 +39,7 @@ class CANRManager { bool dialogSaidWait = false; SP dialogBox; - void runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID); + void runDialog(const std::string& appName, const std::string appClass, pid_t dialogWmPID); bool isRunning(); void killDialog(); bool isDefunct() const; diff --git a/src/managers/permissions/DynamicPermissionManager.cpp b/src/managers/permissions/DynamicPermissionManager.cpp index af1de990b..a54847737 100644 --- a/src/managers/permissions/DynamicPermissionManager.cpp +++ b/src/managers/permissions/DynamicPermissionManager.cpp @@ -5,6 +5,7 @@ #include "../../Compositor.hpp" #include "../../config/ConfigValue.hpp" #include "../../helpers/MiscFunctions.hpp" +#include "../../i18n/Engine.hpp" #include using namespace Hyprutils::String; @@ -57,17 +58,6 @@ static const char* permissionToString(eDynamicPermissionType type) { return "error"; } -static const char* permissionToHumanString(eDynamicPermissionType type) { - switch (type) { - case PERMISSION_TYPE_UNKNOWN: return "An application {} is requesting an unknown permission."; - case PERMISSION_TYPE_SCREENCOPY: return "An application {} is trying to capture your screen.

Do you want to allow it to do so?"; - case PERMISSION_TYPE_PLUGIN: return "An application {} is trying to load a plugin: {}.

Do you want to load it?"; - case PERMISSION_TYPE_KEYBOARD: return "A new keyboard has been plugged in: {}.

Do you want to allow it to operate?"; - } - - return "error"; -} - static const char* specialPidToString(eSpecialPidTypes type) { switch (type) { case SPECIAL_PID_TYPE_CONFIG: return "config"; @@ -244,39 +234,41 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s rule->m_pid = pid; - std::string description = ""; + std::string appName = ""; if (binaryPath.empty()) - description = std::format(std::runtime_format(permissionToHumanString(type)), std::format("unknown application (wayland client ID 0x{:x})", rc(client))); + appName = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, {{"wayland_id", std::format("{:x}", rc(client))}}); else if (client) { - std::string binaryName = binaryPath.contains("/") ? binaryPath.substr(binaryPath.find_last_of('/') + 1) : binaryPath; - description = std::format(std::runtime_format(permissionToHumanString(type)), std::format("{} ({})", binaryName, binaryPath)); + appName = binaryPath.contains("/") ? binaryPath.substr(binaryPath.find_last_of('/') + 1) : binaryPath; } else { - std::string lookup = ""; if (pid < 0) - lookup = specialPidToString(sc(pid)); + appName = specialPidToString(sc(pid)); else { const auto LOOKUP = binaryNameForPid(pid); - lookup = LOOKUP.value_or("Unknown"); - } - - if (type == PERMISSION_TYPE_PLUGIN) { - const auto LOOKUP = binaryNameForPid(pid); - description = std::format(std::runtime_format(permissionToHumanString(type)), lookup, binaryPath); - } else { - const auto LOOKUP = binaryNameForPid(pid); - description = std::format(std::runtime_format(permissionToHumanString(type)), lookup, binaryPath); + appName = LOOKUP.value_or(I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_UNKNOWN_NAME)); } } + std::string description = ""; + switch (rule->m_type) { + case PERMISSION_TYPE_SCREENCOPY: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}}); break; + case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}, {"plugin", binaryPath}}); break; + case PERMISSION_TYPE_KEYBOARD: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_KEYBOARD, {{"keyboard", binaryPath}}); break; + case PERMISSION_TYPE_UNKNOWN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_UNKNOWN, {{"app", appName}}); break; + } + std::vector options; + const auto ALLOW = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW); + const auto ALLOW_AND_REMEMBER = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER); + const auto ALLOW_ONCE = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW_ONCE); + const auto DENY = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_DENY); if (!binaryPath.empty() && client) { - description += "

Hint: you can set persistent rules for these in the Hyprland config file."; - options = {"Deny", "Allow and remember app", "Allow once"}; + description += std::format("

{}", I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_PERSISTENCE_HINT)); + options = {DENY, ALLOW_AND_REMEMBER, ALLOW_ONCE}; } else - options = {"Deny", "Allow"}; + options = {DENY, ALLOW}; - rule->m_dialogBox = CAsyncDialogBox::create("Permission request", description, options); + rule->m_dialogBox = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_TITLE), description, options); rule->m_dialogBox->m_priority = true; if (!rule->m_dialogBox) { @@ -286,7 +278,7 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s } rule->m_promise = rule->m_dialogBox->open(); - rule->m_promise->then([r = WP(rule), binaryPath](SP> pr) { + rule->m_promise->then([r = WP(rule), binaryPath, ALLOW, ALLOW_AND_REMEMBER, ALLOW_ONCE, DENY](SP> pr) { if (!r) return; @@ -303,15 +295,15 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s Debug::log(TRACE, "CDynamicPermissionRule: user returned {}", result); - if (result.starts_with("Allow once")) + if (result.starts_with(ALLOW_ONCE)) r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW; - else if (result.starts_with("Deny")) { + else if (result.starts_with(DENY)) { r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_DENY; r->m_binaryPath = binaryPath; - } else if (result.starts_with("Allow and remember")) { + } else if (result.starts_with(ALLOW_AND_REMEMBER)) { r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW; r->m_binaryPath = binaryPath; - } else if (result.starts_with("Allow")) + } else if (result.starts_with(ALLOW)) r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW; if (r->m_promiseResolverForExternal) diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index 740b2cce8..27e8232ec 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -9,6 +9,7 @@ #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/permissions/DynamicPermissionManager.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../i18n/Engine.hpp" CPluginSystem::CPluginSystem() { g_pFunctionHookSystem = makeUnique(); @@ -224,7 +225,8 @@ void CPluginSystem::updateConfigPlugins(const std::vector& plugins, if (result->hasError()) { const auto NAME = path.contains('/') ? path.substr(path.find_last_of('/') + 1) : path; Debug::log(ERR, "CPluginSystem::updateConfigPlugins: failed to load plugin {}: {}", NAME, result->error()); - g_pHyprNotificationOverlay->addNotification(std::format("Failed to load plugin {}: {}", NAME, result->error()), CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR); + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, {{"name", NAME}, {"error", result->error()}}), + CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR); return; } diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index baf69b968..548b2f8b8 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -24,6 +24,7 @@ #include "../managers/CursorManager.hpp" #include "../helpers/fs/FsUtils.hpp" #include "../helpers/MainLoopExecutor.hpp" +#include "../i18n/Engine.hpp" #include "debug/HyprNotificationOverlay.hpp" #include "hyprerror/HyprError.hpp" #include "pass/TexPassElement.hpp" @@ -1006,7 +1007,7 @@ bool CHyprOpenGLImpl::initShaders() { prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCCM, true, true); if (m_shadersInitialized && m_cmSupported && prog == 0) - g_pHyprNotificationOverlay->addNotification("CM shader reload failed, falling back to rgba/rgbx", CHyprColor{}, 15000, ICON_WARNING); + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_CM_RELOAD_FAILED), CHyprColor{}, 15000, ICON_WARNING); m_cmSupported = prog > 0; if (m_cmSupported) { diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index c13a54d16..9a2410f3a 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -26,6 +26,7 @@ #include "../hyprerror/HyprError.hpp" #include "../debug/HyprDebugOverlay.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../i18n/Engine.hpp" #include "helpers/CursorShapes.hpp" #include "helpers/Monitor.hpp" #include "pass/TexPassElement.hpp" @@ -1576,7 +1577,8 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { Debug::log(WARN, "Wide color gamut is enabled but the display is not in 10bit mode"); static bool shown = false; if (!shown) { - g_pHyprNotificationOverlay->addNotification("Wide color gamut is enabled but the display is not in 10bit mode", CHyprColor{}, 15000, ICON_WARNING); + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, {{"name", pMonitor->m_name}}), CHyprColor{}, 15000, + ICON_WARNING); shown = true; } } From d52639fdfaedd520515a8f46e00d9b8881d40819 Mon Sep 17 00:00:00 2001 From: Virt <41426325+VirtCode@users.noreply.github.com> Date: Sun, 16 Nov 2025 16:54:43 +0100 Subject: [PATCH 10/56] i18n: init german translations (#12323) --- src/i18n/Engine.cpp | 79 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 716d3fc31..93eccf475 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -59,6 +59,85 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("en_US", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader reload failed, falling back to rgba/rgbx."); huEngine->registerEntry("en_US", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: wide color gamut is enabled but the display is not in 10-bit mode."); + // de_DE (German) + huEngine->registerEntry("de_DE", TXT_KEY_ANR_TITLE, "Anwendung Reagiert Nicht"); + huEngine->registerEntry("de_DE", TXT_KEY_ANR_CONTENT, "Eine Anwendung {title} - {class} reagiert nicht.\nWas möchten Sie damit tun?"); + huEngine->registerEntry("de_DE", TXT_KEY_ANR_OPTION_TERMINATE, "Beenden"); + huEngine->registerEntry("de_DE", TXT_KEY_ANR_OPTION_WAIT, "Warten"); + huEngine->registerEntry("de_DE", TXT_KEY_ANR_PROP_UNKNOWN, "(unbekannt)"); + + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Eine Anwendung {app} fordert eine unbekannte Berechtigung an."); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Eine Anwendung {app} versucht Ihren Bildschrim aufzunehmen.\n\nMöchten Sie dies erlauben?"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Eine Anwendung {app} versucht ein Plugin zu laden: {plugin}.\n\nMöchten Sie dies erlauben?"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Eine neue Tastatur wurde erkannt: {keyboard}.\n\nMöchten Sie diese in Betrieb nehmen?"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(unbekannt)"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_TITLE, "Berechtigungsanfrage"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tip: Sie können dafür permanente Regeln in der Hyprland-Konfigurationsdatei festlegen."); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_ALLOW, "Erlauben"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Erlauben und merken"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_ALLOW_ONCE, "Einmal erlauben"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_DENY, "Verweigern"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Unbekannte Anwendung (wayland client ID {wayland_id})"); + + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Ihre XDG_CURRENT_DESKTOP umgebung scheint extern gemanagt zu werden, und der aktuelle Wert ist {value}.\nDies könnte zu Problemen führen sofern es " + "nicht absichtlich so ist."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_NO_GUIUTILS, + "Ihr System hat hyprland-guiutils nicht installiert. Dies ist eine Laufzeitabhängigkeit für einige Dialoge. Es ist empfohlen diese zu installieren."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland konnte {count} essentielle Ressource nicht laden, geben Sie dem Packager ihrer Distribution die Schuld für ein schlechtes Package!"; + return "Hyprland konnte {count} essentielle Ressroucen nicht laden, geben Sie dem Packager ihrer Distribution die Schuld für ein schlechtes Package!"; + }); + huEngine->registerEntry( + "de_DE", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Ihr Bildschirmlayout ist fehlerhaft aufgesetzt. Der Bildschirm {name} überlappt mit anderen Bildschirm(en) im Layout.\nBitte siehe im Wiki (Monitors Seite) für " + "mehr Informationen. Dies wird zu Problemen führen."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Bildschirm {name} konnte keinen der angeforderten Modi setzen fällt auf den Modus {mode} zurück."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Ungültiger Skalierungsfaktor {scale} für Bildschirm {name}, es wird der empfohlene Faktor {fixed_scale} verwendet."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Plugin {name} konnte nicht geladen werden: {error}"); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader konnte nicht neu geladen werden und es wird auf rgba/rgbx zurückgefallen."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Bildschirm {name}: wide color gamut ist aktiviert aber der Bildschirm ist nicht im 10-bit Modus."); + + // de_CH (Swiss German) + huEngine->registerEntry("de_CH", TXT_KEY_ANR_TITLE, "Aawändig Reagiert Ned"); + huEngine->registerEntry("de_CH", TXT_KEY_ANR_CONTENT, "En Aawändig {title} - {class} reagiert ned.\nWas wend Sie demet mache?"); + huEngine->registerEntry("de_CH", TXT_KEY_ANR_OPTION_TERMINATE, "Beände"); + huEngine->registerEntry("de_CH", TXT_KEY_ANR_OPTION_WAIT, "Warte"); + huEngine->registerEntry("de_CH", TXT_KEY_ANR_PROP_UNKNOWN, "(onbekannt)"); + + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "En Aawändig {app} fordert en onbekannti Berächtigong aa."); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "En Aawändig {app} versuecht Ehre Beldscherm uufznäh.\n\nWend Sie das erlaube?"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "En Aawändig {app} versuecht es Plugin z'lade: {plugin}.\n\nWend Sie das erlaube?"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "En neui Tastatur esch erkönne worde: {keyboard}.\n\nWend sie die in Betreb nä?"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(onbekannt)"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_TITLE, "Berächtigongsaafrog"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tip: Sie chönd permanenti Regle deför i ehrere Hyprland-Konfigurationsdatei festlegge."); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_ALLOW, "Erlaube"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Erlaube ond merke"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_ALLOW_ONCE, "Einisch erlaube"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_DENY, "Verweigere"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Onbekannti Aawändig (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "de_CH", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Ehri XDG_CURRENT_DESKTOP omgäbig schiint extern gmanagt z'wärde, ond de aktuelli Wärt esch {value}.\nDas chönnt zo Problem füehre sofärn das ned absechtlech so esch."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_NO_GUIUTILS, + "Ehres System hed hyprland-guiutils ned installiert. Das esch en Laufziitabhängigkeit för es paar Dialog. Es werd empfohle sie z'installiere."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_FAILED_ASSETS, + "Hyprland hed {count} essentielli Ressource ned chönne lade, gäbed Sie im Packager vo ehrere Distribution schold för es schlächts Package!"); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Ehres Beldschermlayout esch fählerhaft uufgsetzt. De Beldscherm {name} öberlappt met andere Beldscherm(e) im Layout.\nBitte lueged sie im Wiki " + "(Monitors Siite) för meh Informatione. Das werd zo Problem füehre."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "De Beldscherm {name} hed keine vode aagforderete Modi chönne setze, ond fallt uf de Modus {mode} zrogg."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Ongöltige Skalierigsfaktor {scale} för de Beldscherm {name}, es werd de empfohleni Faktor {fixed_scale} verwändet."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "S Plugin {name} hed ned chönne glade wärde: {error}"); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader hed ned chönne neu glade wärde, es werd uf rgba/rgbx zrogggfalle."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Beldscherm {name}: wide color gamut esch aktiviert aber de Beldscherm esch ned im 10-bit Modus."); + // it_IT (Italian) huEngine->registerEntry("it_IT", TXT_KEY_ANR_TITLE, "L'applicazione non risponde"); huEngine->registerEntry("it_IT", TXT_KEY_ANR_CONTENT, "Un'applicazione {title} - {class} non risponde.\nCosa vuoi fare?"); From 5b373ea9f52db098cc9e7eb7c742a187ee21a487 Mon Sep 17 00:00:00 2001 From: Lumine Date: Sun, 16 Nov 2025 18:10:40 +0100 Subject: [PATCH 11/56] i18n: add French translations (#12330) --- src/i18n/Engine.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 93eccf475..1a9c8cb44 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -138,6 +138,46 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader hed ned chönne neu glade wärde, es werd uf rgba/rgbx zrogggfalle."); huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Beldscherm {name}: wide color gamut esch aktiviert aber de Beldscherm esch ned im 10-bit Modus."); + // fr_FR (French) + huEngine->registerEntry("fr_FR", TXT_KEY_ANR_TITLE, "L'application ne répond plus"); + huEngine->registerEntry("fr_FR", TXT_KEY_ANR_CONTENT, "L'application {title} - {class} ne répond plus.\nQue voulez-vous faire?"); + huEngine->registerEntry("fr_FR", TXT_KEY_ANR_OPTION_TERMINATE, "Forcer l'arrêt"); + huEngine->registerEntry("fr_FR", TXT_KEY_ANR_OPTION_WAIT, "Attendre"); + huEngine->registerEntry("fr_FR", TXT_KEY_ANR_PROP_UNKNOWN, "(inconnu)"); + + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Une application {app} demande une autorisation inconnue."); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Une application {app} tente de capturer votre écran.\n\nVoulez-vous l'y autoriser?"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Une application {app} tente de charger un module : {plugin}.\n\nVoulez-vous l'y autoriser?"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Un nouveau clavier a été détecté : {keyboard}.\n\nVouslez-vous l'autoriser à fonctioner?"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(inconnu)"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_TITLE, "Demande d'autorisation"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Astuce: vous pouvez définir des règles persistantes dans le fichier de configuration de Hyprland."); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_ALLOW, "Autoriser"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Autoriser et mémoriser"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_ALLOW_ONCE, "Autoriser une fois"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_DENY, "Refuser"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Application inconnue (ID client wayland {wayland_id})"); + + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Votre variable d'environnement XDG_CURRENT_DESKTOP semble être gérée de manière externe, et sa valeur actuelle est {value}.\nCela peut provoquer des " + "problèmes si ce n'est pas intentionnel."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_NO_GUIUTILS, + "Vous système n'a pas hyprland-guiutils installé. C'est une dépendance d'éxécution pour certains dialogues. Envisagez de l'installer."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland n'a pas pu charger {count} ressource essentielle, cela indique très probablement un problème dans le paquet de votre distribution."; + return "Hyprland n'a pas pu charger {count} ressources essentielles, cela indique très probablement un problème dans le paquet de votre distribution."; + }); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Votre disposition d'écrans est incorrecte. Le moniteur {name} chevauche un ou plusieurs autres.\nVeuillez consulter le wiki (page Moniteurs) pour" + "en savoir plus. Cela causera des problèmes."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Le moniteur {name} n'a pu appliquer aucun des modes demandés, retour au mode {mode}."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Échelle invalide pour le moniteur {name}: {scale}. Utilisation de l'échelle suggérée: {fixed_scale}."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Échec du chargement du module {name} : {error}"); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Le rechargement du shader CM a échoué, retour aux formats rgba/rgbx"); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Moniteur {name} : l'espace colorimétrique étendu est activé, mais l'écran n'est pas en mode 10-bits."); + // it_IT (Italian) huEngine->registerEntry("it_IT", TXT_KEY_ANR_TITLE, "L'applicazione non risponde"); huEngine->registerEntry("it_IT", TXT_KEY_ANR_CONTENT, "Un'applicazione {title} - {class} non risponde.\nCosa vuoi fare?"); From b04e8e00b0909445c733272ca80472a9ade79a38 Mon Sep 17 00:00:00 2001 From: Giacomo Zama <32515303+giacomozama@users.noreply.github.com> Date: Sun, 16 Nov 2025 18:43:55 +0100 Subject: [PATCH 12/56] cursor: fix m_cursorSurfaceInfo not being updated while a cursor override is set (#12327) --- src/managers/input/InputManager.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 1f1b0d0d0..f44f21015 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -50,9 +50,6 @@ CInputManager::CInputManager() { m_listeners.setCursorShape = PROTO::cursorShape->m_events.setShape.listen([this](const CCursorShapeProtocol::SSetShapeEvent& event) { - if (!cursorImageUnlocked()) - return; - if (!g_pSeatManager->m_state.pointerFocusResource) return; @@ -66,6 +63,9 @@ CInputManager::CInputManager() { m_cursorSurfaceInfo.name = event.shapeName; m_cursorSurfaceInfo.hidden = false; + if (!cursorImageUnlocked()) + return; + g_pHyprRenderer->setCursorFromName(m_cursorSurfaceInfo.name); }); @@ -653,9 +653,6 @@ void CInputManager::onMouseButton(IPointer::SButtonEvent e) { } void CInputManager::processMouseRequest(const CSeatManager::SSetCursorEvent& event) { - if (!cursorImageUnlocked()) - return; - Debug::log(LOG, "cursorImage request: surface {:x}", rc(event.surf.get())); if (event.surf != m_cursorSurfaceInfo.wlSurface->resource()) { @@ -675,6 +672,9 @@ void CInputManager::processMouseRequest(const CSeatManager::SSetCursorEvent& eve m_cursorSurfaceInfo.name = ""; + if (!cursorImageUnlocked()) + return; + g_pHyprRenderer->setCursorSurface(m_cursorSurfaceInfo.wlSurface, event.hotspot.x, event.hotspot.y); } From 9321f52e071e33d57df756928b379b6726fedd43 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Sun, 16 Nov 2025 18:28:16 +0000 Subject: [PATCH 13/56] CI: Add AI translation checks (#12342) Adds AI-driven translation checks for translation MRs. Uses GPT-5-Mini. Runs on a new translation MR, or can be manually triggered by me with "ai, please recheck" --- .github/workflows/translation-ai-check.yml | 114 +++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 .github/workflows/translation-ai-check.yml diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml new file mode 100644 index 000000000..ec8f564c2 --- /dev/null +++ b/.github/workflows/translation-ai-check.yml @@ -0,0 +1,114 @@ +name: AI Translation Check + +on: + pull_request: + types: + - opened + paths: + - 'src/i18n/**' + issue_comment: + types: + - created + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + review: + name: Review Translation + if: > + ${{ + github.event_name == 'pull_request' || + ( + github.event_name == 'issue_comment' && + github.event.action == 'created' && + github.event.issue.pull_request != null && + github.event.comment.user.login == 'vaxerski' && + github.event.comment.body == 'ai, please recheck' + ) + }} + runs-on: ubuntu-latest + env: + OPENAI_MODEL: gpt-5-mini + SYSTEM_PROMPT: | + You are a programmer and a translator. Your job is to review the attached patch for adding translation to a piece of software and make sure the submitted translation is not malicious, and that it makes sense. If the translation is not malicious, and doesn't contain obvious grammatical mistakes, say "Translation check OK". Otherwise, say "Translation check not ok" and list bad entries. + Examples of bad translations include obvious trolling (slurs, etc) or nonsense sentences. Meaningful improvements may be suggested, but if there are only minor improvements, just reply with "Translation check OK". Do not provide anything but the result and (if applicable) the bad entries or improvements. + + AI_PROMPT: Translation patch below. + + steps: + - name: Download combined PR diff + id: get_diff + run: | + # Get the combined diff for the entire PR + curl -sSL \ + "${{ github.event.pull_request.diff_url }}" \ + -o pr.diff + + # Compute character length + LEN=$(wc -c < pr.diff | tr -d ' ') + echo "len=$LEN" >> "$GITHUB_OUTPUT" + if [ "$LEN" -gt 25000 ]; then + echo "too_long=true" >> "$GITHUB_OUTPUT" + else + echo "too_long=false" >> "$GITHUB_OUTPUT" + fi + + echo "got diff:" + cat pr.diff + + - name: Comment when diff length exceeded + if: steps.get_diff.outputs.too_long == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + jq -n --arg body "Diff length exceeded, can't query API" '{body: ("AI translation check result:\n\n" + $body)}' > body.json + curl -sS -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ + --data @body.json + + - name: Query OpenAI and post review + if: steps.get_diff.outputs.too_long == 'false' + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENAI_MODEL: ${{ env.OPENAI_MODEL }} + SYSTEM_PROMPT: ${{ env.SYSTEM_PROMPT }} + AI_PROMPT: ${{ env.AI_PROMPT }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Prepare OpenAI chat request payload (embed diff safely) + jq -n \ + --arg model "$OPENAI_MODEL" \ + --arg sys "$SYSTEM_PROMPT" \ + --arg prompt "$AI_PROMPT" \ + --rawfile diff pr.diff \ + '{model:$model, + messages:[ + {role:"system", content:$sys}, + {role:"user", content: ($prompt + "\n\n```diff\n" + $diff + "\n```")} + ] + }' > payload.json + + # Call OpenAI + curl -sS https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d @payload.json > response.json + + # Extract response text + COMMENT=$(jq -r '.choices[0].message.content // empty' response.json) + if [ -z "$COMMENT" ]; then + COMMENT="AI did not return a response." + fi + + # Post the review as a PR comment + jq -n --arg body "$COMMENT" '{body: ("AI translation check result:\n\n" + $body)}' > body.json + curl -sS -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ + --data @body.json From c7e14ecd301980d440e8ecb6ea502fbb60802474 Mon Sep 17 00:00:00 2001 From: Aditya An1l <140952269+aditya-an1l@users.noreply.github.com> Date: Sun, 16 Nov 2025 23:58:50 +0530 Subject: [PATCH 14/56] i18n: Add Hindi translations (#12324) --- src/i18n/Engine.cpp | 57 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 1a9c8cb44..c2cb7577c 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -178,6 +178,63 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Le rechargement du shader CM a échoué, retour aux formats rgba/rgbx"); huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Moniteur {name} : l'espace colorimétrique étendu est activé, mais l'écran n'est pas en mode 10-bits."); + // hi_IN (Hindi) + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_TITLE, "एप्लिकेशन प्रतिक्रिया नहीं दे रहा है"); + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_CONTENT, + "एक एप्लिकेशन {title} - {class} प्रतिक्रिया नहीं दे रहा " + "है।\nआप इसके साथ क्या करना चाहेंगे?"); + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_OPTION_TERMINATE, "समाप्त करें"); + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_OPTION_WAIT, "इंतजार करें"); + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_PROP_UNKNOWN, "(अज्ञात)"); + + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "एक एप्लिकेशन {app} एक अज्ञात अनुमति का अनुरोध कर रहा है।"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, + "एक एप्लिकेशन {app} आपकी स्क्रीन कैप्चर करने की " + "कोशिश कर रहा है।\n\nक्या आप इसे अनुमति देना चाहते हैं?"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "एक एप्लिकेशन {app} एक प्लगइन लोड करने की कोशिश कर रहा है: " + "{plugin}.\n\nक्या आप इसे अनुमति देना चाहते हैं?"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, + "नया कीबोर्ड पाया गया: {keyboard}.\n\nक्या आप " + "इसे काम करने की अनुमति देना चाहते हैं?"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(अज्ञात)"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_TITLE, "अनुमति अनुरोध"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "संकेत: आप Hyprland कॉन्फ़िग फ़ाइल में इनके लिए स्थायी नियम सेट कर सकते हैं।"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_ALLOW, "अनुमति दें"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "अनुमति दें और याद रखें"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_ALLOW_ONCE, "एक बार अनुमति दें"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_DENY, "अस्वीकार करें"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "अज्ञात एप्लिकेशन (wayland क्लाइंट ID {wayland_id})"); + + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "आपका XDG_CURRENT_DESKTOP परिवेश बाहरी रूप से प्रबंधित लगता है, और वर्तमान मान " + "{value} है।\nयह समस्या पैदा कर सकता " + "है जब तक कि यह जानबूझकर न किया गया हो।"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_NO_GUIUTILS, + "आपके सिस्टम में hyprland-guiutils इंस्टॉल नहीं है। यह कुछ संवादों के लिए एक रनटाइम " + "निर्भरता है। इसे इंस्टॉल करने पर विचार करें।"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland {count} आवश्यक संसाधन लोड करने में विफल रहा, अपने डिस्ट्रो " + "के पैकेजर को पैकेजिंग में खराब काम करने का दोष दें!"; + return "Hyprland {count} आवश्यक संसाधनों को लोड करने में विफल रहा, अपने " + "डिस्ट्रो के पैकेजर को पैकेजिंग में खराब काम करने का दोष दें!"; + }); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "आपका मॉनिटर लेआउट गलत तरीके से सेट है। मॉनिटर {name} लेआउट में अन्य मॉनिटर(ओं) के " + "साथ ओवरलैप कर रहा है।\nकृपया विकि " + " (Monitors पेज) देखें। यह समस्याएँ पैदा करेगा।"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + "मॉनिटर {name} ने किसी भी अनुरोधित मोड को सेट करने में " + "विफल रहा, मोड {mode} पर वापस जा रहा है।"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "मॉनिटर {name} को अवैध स्केल दिया गया: {scale}, सुझाया " + "गया स्केल इस्तेमाल किया जा रहा है: {fixed_scale}"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "प्लगइन {name} लोड करने में विफल: {error}"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM शेडर रीलोड विफल हुआ, rgba/rgbx पर वापस जा रहा है।"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "मॉनिटर {name}: वाइड कलर गैम सक्षम है लेकिन डिस्प्ले 10-बिट मोड में नहीं है।"); + // it_IT (Italian) huEngine->registerEntry("it_IT", TXT_KEY_ANR_TITLE, "L'applicazione non risponde"); huEngine->registerEntry("it_IT", TXT_KEY_ANR_CONTENT, "Un'applicazione {title} - {class} non risponde.\nCosa vuoi fare?"); From d2d1613e4f13a099f5d7ce126fb5f0e40659743b Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Sun, 16 Nov 2025 19:32:32 +0200 Subject: [PATCH 15/56] Nix: fix GIT_* env vars --- nix/default.nix | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index 0fe57191a..e69f22424 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -106,11 +106,13 @@ in sed -i "s#@PREFIX@/##g" hyprland.pc.in ''; - COMMITS = revCount; - DATE = date; - DIRTY = optionalString (commit == "") "dirty"; - HASH = commit; - TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}"; + env = { + GIT_COMMITS = revCount; + GIT_COMMIT_DATE = date; + GIT_COMMIT_HASH = commit; + GIT_DIRTY = if (commit == "") then "clean" else "dirty"; + GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}"; + }; depsBuildBuild = [ pkg-config From a6b877fec29cf9cd24172df8c26a0858b31aaf1b Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Sun, 16 Nov 2025 19:41:18 +0200 Subject: [PATCH 16/56] CMake: prepopulate GIT vars from env --- CMakeLists.txt | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7c97b7b5..f641813ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,13 +128,41 @@ set(HYPRGRAPHICS_VERSION "${hyprgraphics_dep_VERSION}") find_package(Git QUIET) -set(GIT_COMMIT_HASH "unknown") -set(GIT_BRANCH "unknown") -set(GIT_COMMIT_MESSAGE "unknown") -set(GIT_COMMIT_DATE "unknown") -set(GIT_DIRTY "unknown") -set(GIT_TAG "unknown") -set(GIT_COMMITS "0") +# Populate variables with env vars if present +set(GIT_COMMIT_HASH "$ENV{GIT_COMMIT_HASH}") +if(NOT GIT_COMMIT_HASH) + set(GIT_COMMIT_HASH "unknown") +endif() + +set(GIT_BRANCH "$ENV{GIT_BRANCH}") +if(NOT GIT_BRANCH) + set(GIT_BRANCH "unknown") +endif() + +set(GIT_COMMIT_MESSAGE "$ENV{GIT_COMMIT_MESSAGE}") +if(NOT GIT_COMMIT_MESSAGE) + set(GIT_COMMIT_MESSAGE "unknown") +endif() + +set(GIT_COMMIT_DATE "$ENV{GIT_COMMIT_DATE}") +if(NOT GIT_COMMIT_DATE) + set(GIT_COMMIT_DATE "unknown") +endif() + +set(GIT_DIRTY "$ENV{GIT_DIRTY}") +if(NOT GIT_DIRTY) + set(GIT_DIRTY "unknown") +endif() + +set(GIT_TAG "$ENV{GIT_TAG}") +if(NOT GIT_TAG) + set(GIT_TAG "unknown") +endif() + +set(GIT_COMMITS "$ENV{GIT_COMMITS}") +if(NOT GIT_COMMITS) + set(GIT_COMMITS "0") +endif() if(Git_FOUND) execute_process( From 15b4b1dd915879a8ca17be7648f00ca76c0774cd Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 16 Nov 2025 18:43:12 +0000 Subject: [PATCH 17/56] ci: fix comment workflow for translations --- .github/workflows/translation-ai-check.yml | 35 +++++++++++++--------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index ec8f564c2..22945d500 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -18,17 +18,7 @@ permissions: jobs: review: name: Review Translation - if: > - ${{ - github.event_name == 'pull_request' || - ( - github.event_name == 'issue_comment' && - github.event.action == 'created' && - github.event.issue.pull_request != null && - github.event.comment.user.login == 'vaxerski' && - github.event.comment.body == 'ai, please recheck' - ) - }} + if: ${{ github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }} runs-on: ubuntu-latest env: OPENAI_MODEL: gpt-5-mini @@ -39,12 +29,26 @@ jobs: AI_PROMPT: Translation patch below. steps: + - name: Determine PR number + id: pr + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + else + echo "number=${{ github.event.issue.number }}" >> "$GITHUB_OUTPUT" + fi + - name: Download combined PR diff id: get_diff + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.pr.outputs.number }} run: | # Get the combined diff for the entire PR curl -sSL \ - "${{ github.event.pull_request.diff_url }}" \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3.diff" \ + "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" \ -o pr.diff # Compute character length @@ -63,12 +67,13 @@ jobs: if: steps.get_diff.outputs.too_long == 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.pr.outputs.number }} run: | jq -n --arg body "Diff length exceeded, can't query API" '{body: ("AI translation check result:\n\n" + $body)}' > body.json curl -sS -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Content-Type: application/json" \ - "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ + "https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \ --data @body.json - name: Query OpenAI and post review @@ -79,6 +84,7 @@ jobs: SYSTEM_PROMPT: ${{ env.SYSTEM_PROMPT }} AI_PROMPT: ${{ env.AI_PROMPT }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.pr.outputs.number }} run: | # Prepare OpenAI chat request payload (embed diff safely) jq -n \ @@ -107,8 +113,9 @@ jobs: # Post the review as a PR comment jq -n --arg body "$COMMENT" '{body: ("AI translation check result:\n\n" + $body)}' > body.json + echo "CURLing https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" curl -sS -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Content-Type: application/json" \ - "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ + "https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \ --data @body.json From c02a6184d36661b9fffd96fa5d543339c694c549 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 16 Nov 2025 19:32:26 +0000 Subject: [PATCH 18/56] CI: add a fail note to translation ci --- .github/workflows/translation-ai-check.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index 22945d500..d0fbd3e44 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -111,8 +111,14 @@ jobs: COMMENT="AI did not return a response." fi + # If failed, add a note + $ADDITIONAL_NOTE = "" + if [[ $COMMENT == *"not ok"* ]]; then + $ADDITIONAL_NOTE = "\n\nPlease note this check is a guideline, not a hard requirement. It is here to help you translate. If you disagree with some points, just state that. Any typos should be fixed." + fi + # Post the review as a PR comment - jq -n --arg body "$COMMENT" '{body: ("AI translation check result:\n\n" + $body)}' > body.json + jq -n --arg body "$COMMENT" --arg note "$ADDITIONAL_NOTE" '{body: ("AI translation check result:\n\n" + $body + $note)}' > body.json echo "CURLing https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" curl -sS -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ From f0de61ca21bb4757ae2f18e0382c8829638745d9 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 16 Nov 2025 19:34:36 +0000 Subject: [PATCH 19/56] CI: run translator in pull_request_target for comment access --- .github/workflows/translation-ai-check.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index d0fbd3e44..1b4636b20 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -1,7 +1,7 @@ name: AI Translation Check on: - pull_request: + pull_request_target: types: - opened paths: @@ -18,7 +18,7 @@ permissions: jobs: review: name: Review Translation - if: ${{ github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }} + if: ${{ github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }} runs-on: ubuntu-latest env: OPENAI_MODEL: gpt-5-mini @@ -32,8 +32,8 @@ jobs: - name: Determine PR number id: pr run: | - if [ "${{ github.event_name }}" = "pull_request" ]; then - echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + if [ "${{ github.event_name }}" = "pull_request_target" ]; then + echo "number=${{ github.event.pull_request_target.number }}" >> "$GITHUB_OUTPUT" else echo "number=${{ github.event.issue.number }}" >> "$GITHUB_OUTPUT" fi From 7a6177532bc5bff01805ff8ffe0aa3d68c0b085e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Salcedo=20Garc=C3=ADa?= Date: Sun, 16 Nov 2025 21:09:08 +0100 Subject: [PATCH 20/56] i18n: add Spanish translations (#12334) --- src/i18n/Engine.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index c2cb7577c..b55592f56 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -138,6 +138,49 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader hed ned chönne neu glade wärde, es werd uf rgba/rgbx zrogggfalle."); huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Beldscherm {name}: wide color gamut esch aktiviert aber de Beldscherm esch ned im 10-bit Modus."); + // es (Spanish) + huEngine->registerEntry("es", TXT_KEY_ANR_TITLE, "La aplicación no responde"); + huEngine->registerEntry("es", TXT_KEY_ANR_CONTENT, "Una aplicación {title} - {class} no responde.\n¿Qué quieres hacer?"); + huEngine->registerEntry("es", TXT_KEY_ANR_OPTION_TERMINATE, "Terminar"); + huEngine->registerEntry("es", TXT_KEY_ANR_OPTION_WAIT, "Esperar"); + huEngine->registerEntry("es", TXT_KEY_ANR_PROP_UNKNOWN, "(desconocido)"); + + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Una aplicación {app} está solicitando un permiso desconocido."); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Una aplicación {app} está intentando capturar la pantalla.\n\n¿Quieres permitirlo?"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Una aplicación {app} está intentando cargar un plugin: {plugin}.\n\n¿Quieres permitirlo?"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Se ha detectado un nuevo teclado: {keyboard}.\n\n¿Quieres permitir su funcionamiento?"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(desconocido)"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_TITLE, "Solicitud de permiso"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Sugerencia: puedes establecer reglas persistentes para estos en el archivo de configuración de Hyprland."); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_ALLOW, "Permitir"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Permitir y recordar"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_ALLOW_ONCE, "Permitir una vez"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_DENY, "Denegar"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Aplicación desconocida (wayland client ID {wayland_id})"); + + huEngine->registerEntry("es", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "La variable de entorno XDG_CURRENT_DESKTOP parece estar gestionada externamente, y el valor actual es {value}.\nEsto podría causar problemas, a menos " + "que sea intencionado."); + huEngine->registerEntry( + "es", TXT_KEY_NOTIF_NO_GUIUTILS, + "Tu sistema no tiene instalado hyprland-guiutils. Se trata de una dependencia de tiempo de ejecución para algunos diálogos. Considera la posibilidad de instalarlo."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "No se ha podido cargar {count} recurso clave, ¡culpa a tu empaquetador por hacer un mal trabajo!"; + return "No se ha podido cargar {count} recursos clave, ¡culpa a tu empaquetador por hacer un mal trabajo!"; + }); + huEngine->registerEntry( + "es", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "La configuración de su monitor no es correcta. El monitor {name} se superpone con otros monitores en la configuración. Consulte la wiki (página Monitors, en inglés) " + "para obtener más información. Esto provocará problemas."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + "El monitor {name} no ha podido configurar ninguno de los modos solicitados, por lo que ha recurrido al modo {mode}."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Escala no válida pasada al monitor {name}: {scale}, utilizando la escala sugerida: {fixed_scale}"); + huEngine->registerEntry("es", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Error al cargar el plugin {name}: {error}"); + huEngine->registerEntry("es", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Error al recargar el sombreador CM, recurriendo a rgba/rgbx."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: la gama de colores amplia está habilitada, pero la pantalla no está en modo de 10-bit."); + // fr_FR (French) huEngine->registerEntry("fr_FR", TXT_KEY_ANR_TITLE, "L'application ne répond plus"); huEngine->registerEntry("fr_FR", TXT_KEY_ANR_CONTENT, "L'application {title} - {class} ne répond plus.\nQue voulez-vous faire?"); From e948445f6ecd24b330af4cf07a046f44e4d10297 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 16 Nov 2025 20:14:38 +0000 Subject: [PATCH 21/56] CI: minor translation fixes --- .github/workflows/translation-ai-check.yml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index 1b4636b20..81019a417 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -4,8 +4,6 @@ on: pull_request_target: types: - opened - paths: - - 'src/i18n/**' issue_comment: types: - created @@ -29,11 +27,22 @@ jobs: AI_PROMPT: Translation patch below. steps: + - uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + i18n: + - 'src/i18n/**' + + - name: Stop if i18n not changed + if: steps.changes.outputs.i18n != 'true' + run: echo "No i18n changes in this PR; skipping." && exit 0 + - name: Determine PR number id: pr run: | if [ "${{ github.event_name }}" = "pull_request_target" ]; then - echo "number=${{ github.event.pull_request_target.number }}" >> "$GITHUB_OUTPUT" + echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" else echo "number=${{ github.event.issue.number }}" >> "$GITHUB_OUTPUT" fi @@ -112,9 +121,9 @@ jobs: fi # If failed, add a note - $ADDITIONAL_NOTE = "" - if [[ $COMMENT == *"not ok"* ]]; then - $ADDITIONAL_NOTE = "\n\nPlease note this check is a guideline, not a hard requirement. It is here to help you translate. If you disagree with some points, just state that. Any typos should be fixed." + ADDITIONAL_NOTE="" + if [[ "$COMMENT" == *"not ok"* ]]; then + ADDITIONAL_NOTE="\n\nPlease note this check is a guideline, not a hard requirement. It is here to help you translate. If you disagree with some points, just state that. Any typos should be fixed." fi # Post the review as a PR comment From 49c0c97c5a440a7e1bda623cd54d32211349e115 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 16 Nov 2025 20:55:15 +0000 Subject: [PATCH 22/56] CI: fix translator --- .github/workflows/translation-ai-check.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index 81019a417..eebea8a4f 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -27,6 +27,9 @@ jobs: AI_PROMPT: Translation patch below. steps: + - name: Checkout source code + uses: actions/checkout@v5 + - uses: dorny/paths-filter@v3 id: changes with: From 0770494ddfa95fccdae4d7676445c130268a6a1a Mon Sep 17 00:00:00 2001 From: Aivaz Latypov Date: Mon, 17 Nov 2025 01:56:00 +0500 Subject: [PATCH 23/56] i18n: add Russian translations (#12335) --- src/i18n/Engine.cpp | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index b55592f56..4e59f9ad2 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -387,6 +387,45 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nie udało się załadować plugin'a {name}: {error}"); huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Nie udało się przeładować shader'a CM, użyto rgba/rgbx."); huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: skonfigurowano szeroką głębię barw, ale monitor nie jest w trybie 10-bit."); + + // ru_RU (Russian) + huEngine->registerEntry("ru_RU", TXT_KEY_ANR_TITLE, "Приложение не отвечает"); + huEngine->registerEntry("ru_RU", TXT_KEY_ANR_CONTENT, "Приложение {title} - {class} не отвечает.\nЧто вы хотите сделать?"); + huEngine->registerEntry("ru_RU", TXT_KEY_ANR_OPTION_TERMINATE, "Завершить"); + huEngine->registerEntry("ru_RU", TXT_KEY_ANR_OPTION_WAIT, "Подождать"); + huEngine->registerEntry("ru_RU", TXT_KEY_ANR_PROP_UNKNOWN, "(неизвестно)"); + + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Приложение {app} запрашивает неизвестное разрешение."); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Приложение {app} пытается получить доступ к вашему экрану.\n\nРазрешить?"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Приложение {app} пытается загрузить плагин: {plugin}.\n\nРазрешить?"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Обнаружена новая клавиатура: {keyboard}.\n\nРазрешить ей работать?"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(неизвестно)"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_TITLE, "Запрос разрешения"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Подсказка: вы можете настроить постоянные правила для этого в конфигурационном файле Hyprland."); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_ALLOW, "Разрешить"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Разрешить и запомнить"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_ALLOW_ONCE, "Разрешить в этот раз"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_DENY, "Отклонить"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Неизвестное приложение (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "ru_RU", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Переменная окружения XDG_CURRENT_DESKTOP установлена извне, текущее значение: {value}.\nЭто может вызвать проблемы, если только это не сделано намеренно."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_NO_GUIUTILS, "Пакет hyprland-guiutils не установлен. Он необходим для некоторых диалогов. Рекомендуется установить его."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Не удалось загрузить {count} критически важный ресурс, пожалуйтесь мейнтейнеру вашего дистрибутива за кривую сборку пакета!"; + return "Не удалось загрузить {count} критически важных ресурсов, пожалуйтесь мейнтейнеру вашего дистрибутива за кривую сборку пакета!"; + }); + huEngine->registerEntry( + "ru_RU", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Неправильно настроен макет мониторов. Монитор {name} перекрывает другие.\nПодробнее см. в документации (страница Monitors). Это обязательно вызовет проблемы."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Монитор {name} не смог установить ни один из запрошенных режимов, выбран режим {mode}."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Недопустимый масштаб для монитора {name}: {scale}, используется предложенный масштаб: {fixed_scale}"); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Не удалось загрузить плагин {name}: {error}"); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Не удалось перезагрузить CM shader, используется rgba/rgbx."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монитор {name}: расширенный цветовой охват включён, но дисплей не в 10-bit режиме."); } std::string I18n::CI18nEngine::localize(eI18nKeys key, const Hyprutils::I18n::translationVarMap& vars) { From 7910bc42afeeb142305c0dd99b3a42ac73039f85 Mon Sep 17 00:00:00 2001 From: bea4dev <34712108+bea4dev@users.noreply.github.com> Date: Mon, 17 Nov 2025 06:17:05 +0900 Subject: [PATCH 24/56] renderer: fix fractional scale artifacts (#12287) --- src/render/Renderer.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 9a2410f3a..b115bab97 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1134,8 +1134,12 @@ void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SPm_current.bufferSize; - const Vector2D MISALIGNMENT = pSurface->m_current.bufferSize - projSize; + const Vector2D PIXELASUV = Vector2D{1, 1} / pSurface->m_current.bufferSize; + const auto& BUFFER_SIZE = pSurface->m_current.bufferSize; + + // compute MISALIGN from the adjusted UV coordinates. + const Vector2D MISALIGNMENT = (uvBR - uvTL) * BUFFER_SIZE - projSize; + if (MISALIGNMENT != Vector2D{}) uvBR -= MISALIGNMENT * PIXELASUV; } else { From 6e2fe103bc94444a1a4589699bba12aaceec2443 Mon Sep 17 00:00:00 2001 From: Lone Detective Date: Mon, 17 Nov 2025 02:48:17 +0530 Subject: [PATCH 25/56] i18n: add Malayalam translations (#12345) --- src/i18n/Engine.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 4e59f9ad2..f3bdbed6b 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -349,6 +349,51 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CMシェーダーのリロード失敗、rgba/rgbxを使いました。"); huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "画面{name}:広い色域は設定していますけど、画面は10ビットモードに設定されていません。"); + // ml_IN (Malayalam) + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_TITLE, "ആപ്ലിക്കേഷൻ പ്രതികരിക്കുന്നില്ല"); + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_CONTENT, "ആപ്ലിക്കേഷൻ {title} - {class} പ്രതികരിക്കുന്നില്ല.\nഇതിന് നിങ്ങൾ എന്ത് ചെയ്യാൻ ആഗ്രഹിക്കുന്നു?"); + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_OPTION_TERMINATE, "അവസാനിപ്പിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_OPTION_WAIT, "കാത്തിരിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_PROP_UNKNOWN, "(അജ്ഞാതം)"); + + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "ആപ്ലിക്കേഷൻ {app} ഒരു അജ്ഞാത അനുമതി അഭ്യർത്ഥിക്കുന്നു."); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "ആപ്ലിക്കേഷൻ {app} നിങ്ങളുടെ സ്ക്രീൻ പകർത്താൻ ശ്രമിക്കുന്നു.\n\nനിങ്ങൾ അത് അനുവദിക്കണോ?"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "ആപ്ലിക്കേഷൻ {app} ഒരു പ്ലഗിൻ ലോഡ് ചെയ്യാൻ ശ്രമിക്കുന്നു: {plugin}.\n\nഇത് അനുവദിക്കണോ?"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "പുതിയ കീബോർഡ് കണ്ടെത്തി: {keyboard}.\n\nഇത് പ്രവർത്തിക്കാൻ അനുവദിക്കണോ?"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(അജ്ഞാതം)"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_TITLE, "അനുമതി അഭ്യർത്ഥന"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "സൂചന: Hyprland കോൺഫിഗ് ഫയലിൽ സ്ഥിരനിയമങ്ങൾ സജ്ജമാക്കാം."); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_ALLOW, "അനുവദിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "അനുവദിച്ച് ഓർക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_ALLOW_ONCE, "ഒന്നുതവണ അനുവദിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_DENY, "നിരസിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "അജ്ഞാത അപ്ലിക്കേഷൻ (wayland client ID {wayland_id})"); + + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "നിങ്ങളുടെ XDG_CURRENT_DESKTOP പരിസ്ഥിതി പുറത്ത് നിന്ന് നിയന്ത്രിക്കപ്പെടുന്നു, ഇപ്പോഴത്തെ മൂല്യം " + "{value}.\nഇത് ഉദ്ദേശ്യമായല്ലെങ്കിൽ പ്രശ്നങ്ങൾ ഉണ്ടാകും."); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_NO_GUIUTILS, + "നിങ്ങളുടെ സിസ്റ്റത്തിൽ hyprland-guiutils ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ല. ഇത് ചില ഡയലോഗുകൾക്ക് ആവശ്യമായ " + "റൺടൈം ആശ്രയമാണ്. ഇൻസ്റ്റാൾ ചെയ്യുക."); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland {count} പ്രധാന അസറ്റ് ലോഡുചെയ്യാൻ പരാജയപ്പെട്ടു, നിങ്ങളുടെ " + "ഡിസ്‌ട്രോ " + "പാക്കേജർ പിശക് ചെയ്തിരിക്കുന്നു!"; + return "Hyprland {count} പ്രധാന അസറ്റുകൾ ലോഡുചെയ്യാൻ പരാജയപ്പെട്ടു, നിങ്ങളുടെ " + "ഡിസ്‌ട്രോ " + "പാക്കേജർ പിശക് ചെയ്തിരിക്കുന്നു!"; + }); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "മോണിറ്റർ ലേയൗട്ട് തെറ്റാണ്. മോണിറ്റർ {name} മറ്റുള്ളവയുമായ് ഒതുങ്ങുന്നു.\nകൂടുതൽ വിവരങ്ങൾക്ക് Wiki " + "(Monitors page) കാണുക. ഇത് പ്രശ്നങ്ങൾ ഉണ്ടാക്കും."); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "മോണിറ്റർ {name} ആവശ്യപ്പെട്ട മോഡുകൾ സജ്ജമാക്കാൻ പരാജയപ്പെട്ടു, ഇപ്പോൾ {mode} ഉപയോഗിക്കുന്നു."); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "മോണിറ്റർ {name} ന് അസാധുവായ സ്കെയിൽ: {scale}, നിർദ്ദേശിച്ച സ്കെയിൽ: {fixed_scale}"); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "പ്ലഗിൻ {name} ലോഡ് ചെയ്യാൻ പരാജയപ്പെട്ടു: {error}"); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM ഷേഡർ റീലോഡ് പരാജയപ്പെട്ടു, rgba/rgbx ലേക്ക് മാറുന്നു."); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "മോണിറ്റർ {name}: വൈഡ് കളർ ഗാമട്ട് പ്രവർത്തനക്ഷമമാണെങ്കിലും, മോഡ് 10-bit അല്ല."); + // pl_PL (Polish) huEngine->registerEntry("pl_PL", TXT_KEY_ANR_TITLE, "Aplikacja Nie Odpowiada"); huEngine->registerEntry("pl_PL", TXT_KEY_ANR_CONTENT, "Aplikacja {title} - {class} nie odpowiada.\nCo chcesz z nią zrobić?"); From 3534dbdb8908cb40f082e8d3550a1fd6c5466ea8 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sun, 16 Nov 2025 21:19:34 +0000 Subject: [PATCH 26/56] ci: translation note fix --- .github/workflows/translation-ai-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index eebea8a4f..555432796 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -126,7 +126,7 @@ jobs: # If failed, add a note ADDITIONAL_NOTE="" if [[ "$COMMENT" == *"not ok"* ]]; then - ADDITIONAL_NOTE="\n\nPlease note this check is a guideline, not a hard requirement. It is here to help you translate. If you disagree with some points, just state that. Any typos should be fixed." + ADDITIONAL_NOTE=$(echo -ne "\n\nPlease note this check is a guideline, not a hard requirement. It is here to help you translate. If you disagree with some points, just state that. Any typos should be fixed.") fi # Post the review as a PR comment From 11451d68b75b84be49ae085e4c62b5ab894c0da0 Mon Sep 17 00:00:00 2001 From: Eren Date: Mon, 17 Nov 2025 00:40:47 +0300 Subject: [PATCH 27/56] i18n: add Turkish translations (#12331) --- src/i18n/Engine.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index f3bdbed6b..6c5f1b23a 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -471,6 +471,42 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Не удалось загрузить плагин {name}: {error}"); huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Не удалось перезагрузить CM shader, используется rgba/rgbx."); huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монитор {name}: расширенный цветовой охват включён, но дисплей не в 10-bit режиме."); + + // tr_TR (Turkish) + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_TITLE, "Uygulama Yanıt Vermiyor"); + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_CONTENT, "Bir uygulama {title} - {class} yanıt vermiyor.\nBununla ne yapmak istiyorsun?"); + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_OPTION_TERMINATE, "Sonlandır"); + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_OPTION_WAIT, "Bekle"); + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_PROP_UNKNOWN, "(bilinmiyor)"); + + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Bir uygulama {app} bilinmeyen bir izin istiyor."); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Bir uygulama {app} ekran kaydı yapmaya çalışıyor.\n\nİzin vermek istiyor musun?"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Bir uygulama {app} bir eklenti kurmaya çalışıyor: {plugin}.\n\nİzin vermek istiyor musun?"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Yeni bir klavye algılandı: {keyboard}.\n\nÇalışmasına izin vermek istiyor musun?"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(bilinmiyor)"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_TITLE, "İzin isteği"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "İpucu: Hyprland config dosyasında bunlar için kalıcı kurallar atayabilirsin."); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_ALLOW, "İzin ver"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "İzin ver ve seçimimi hatırla"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_ALLOW_ONCE, "Yalnızca bir defa izin ver"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_DENY, "Reddet"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Bilinmeyen uygulama (wayland istemci ID {wayland_id})"); + + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "XDG_CURRENT_DESKTOP ortamın harici olarak yönetiliyor gibi gözüküyor, ve mevcut değeri {value}.\nEğer bu bilinçli değilse sorunlara yol açabilir."); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_NO_GUIUTILS, + "Sisteminde hyprland-guiutils yüklü değil. Bu bazı diyaloglar için bir çalışma zamanı bağımlılığı. İndirmeyi göz önünde bulundurabilirsin."); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_FAILED_ASSETS, + "Hyprland {count} gerekli dosyayı yüklemekte başarısız oldu, kötü bir iş çıkardığı için kullandığın distronun paketleyicisini suçla!"); + huEngine->registerEntry( + "tr_TR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Monitör düzenin yanlış ayarlanmış. Monitör {name} düzenindeki başka monitörlerle çakışıyor.\nLütfen daha fazla bilgi için wiki'ye (Monitörler sayfası) göz at. " + "Bu sorunlara yol açacak."); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitör {name} istenen modları ayarlamada başarısız oldu, {mode} moduna geri dönülüyor."); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Monitöre geçersiz ölçek iletildi {name}: {scale}, önerilen ölçek kullanılıyor: {fixed_scale}"); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "{name} plugini yüklenemedi: {error}"); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader yeniden yüklemesi başarısız, rgba/rgbx'e geri dönülüyor."); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitör {name}: wide color gamut etkinleştirildi ama ekran 10-bit modunda değil."); } std::string I18n::CI18nEngine::localize(eI18nKeys key, const Hyprutils::I18n::translationVarMap& vars) { From 76edcfc66c49a421f10b2878d82049df25fdaedb Mon Sep 17 00:00:00 2001 From: Aliaksiej Date: Mon, 17 Nov 2025 00:57:37 +0100 Subject: [PATCH 28/56] i18n: add Belarusian language (#12358) --- src/i18n/Engine.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 6c5f1b23a..553bf10cc 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -19,6 +19,46 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->setFallbackLocale("en_US"); localeStr = huEngine->getSystemLocale().locale(); + // be_BY (Belarusian) + huEngine->registerEntry("be_BY", TXT_KEY_ANR_TITLE, "Праграма не адказвае"); + huEngine->registerEntry("be_BY", TXT_KEY_ANR_CONTENT, "Праграма {title} - {class} не адказвае.\nШто хочаце з ёй зрабіць?"); + huEngine->registerEntry("be_BY", TXT_KEY_ANR_OPTION_TERMINATE, "Прымусова спыніць"); + huEngine->registerEntry("be_BY", TXT_KEY_ANR_OPTION_WAIT, "Пачакаць"); + huEngine->registerEntry("be_BY", TXT_KEY_ANR_PROP_UNKNOWN, "(невядома)"); + + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Праграма {app} запытвае невядомы дазвол."); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Праграма {app} спрабуе здымаць экран.\n\nЦі хочаце дазволіць?"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Праграма {app} спрабуе загрузіць плагін: {plugin}.\n\nХочаце дазволіць?"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Выяўленая новая клавіятура: {keyboard}.\n\nХочаце дазволіць яе выкарыстанне?"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(невядома)"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_TITLE, "Запыт дазволу"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Падказка: вы можаце задаць пастаянныя правілы для гэтага ў файле канфігурацыі Hyprland."); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_ALLOW, "Дазволіць"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Дазволіць і запомніць"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_ALLOW_ONCE, "Дазволіць аднойчы"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_DENY, "Забараніць"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Невядомая праграма (Ідэнтыфікатар кліента wayland {wayland_id})"); + + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Выглядае, што вашая пераменная асяроддзя XDG_CURRENT_DESKTOP зададзеная звонку, цяперашняе значэнне: {value}.\nГэта можа выклікаць праблемы, калі " + "гэта не зроблена наўмысна."); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_NO_GUIUTILS, + "У вашай сістэме не ўсталяваны hyprland-guiutils, што выкарыстоўваецца для некаторых дыялогавых вокнаў. Разгледзьце ўсталёўку пакета."); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland не змог загрузіць {count} важны рэсурс, вінавацьце ў гэтым адказнага за зборку пакетаў для свайго дыстрыбутыва!"; + return "Hyprland не змог загрузіць {count} важных рэсурсаў, вінавацьце ў гэтым адказнага за зборку пакетаў для свайго дыстрыбутыва!"; + }); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Макет манітораў наладжаны некарэктна. Манітор {name} накладаецца на іншы(я) манітор(ы).\nДля падрабязнасцей звярніцеся да Wiki (Старонка Monitors). " + "Гэта абавязкова створыць праблемы."); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Манітор {name} не змог наладзіць ніводны з запатрабаваных рэжымаў, аварыйна ўжыты рэжым {mode}."); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Няверна зададзены маштаб для манітора {name}: {scale}, ужываецца прапанаваны маштаб: {fixed_scale}"); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Не атрымалася загрузіць плагін {name}: {error}"); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Не атрымалася перазагрузіць шэйдар CM, аварыйна ўжываецца rgba/rgbx."); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Манітор {name}: пашыраны каляровы дыяпазон уключаны, але экран не ў рэжыме 10-біт."); + // en_US (English) huEngine->registerEntry("en_US", TXT_KEY_ANR_TITLE, "Application Not Responding"); huEngine->registerEntry("en_US", TXT_KEY_ANR_CONTENT, "An application {title} - {class} is not responding.\nWhat do you want to do with it?"); From 9d02fe9c23ef1bb583e82af6575f6dadb46bc06f Mon Sep 17 00:00:00 2001 From: Abdul Date: Sun, 16 Nov 2025 23:58:23 +0000 Subject: [PATCH 29/56] i18n: add Arabic (ar) translations (#12352) --- src/i18n/Engine.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 553bf10cc..7fdcec195 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -473,6 +473,53 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Nie udało się przeładować shader'a CM, użyto rgba/rgbx."); huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: skonfigurowano szeroką głębię barw, ale monitor nie jest w trybie 10-bit."); + // ar (Arabic - Modern Standard) + huEngine->registerEntry("ar", TXT_KEY_ANR_TITLE, "التطبيق لا يستجيب"); + huEngine->registerEntry("ar", TXT_KEY_ANR_CONTENT, "التطبيق {title} - {class} لا يستجيب.\nما الذي تريد فعله؟"); + huEngine->registerEntry("ar", TXT_KEY_ANR_OPTION_TERMINATE, "إنهاء"); + huEngine->registerEntry("ar", TXT_KEY_ANR_OPTION_WAIT, "الانتظار"); + huEngine->registerEntry("ar", TXT_KEY_ANR_PROP_UNKNOWN, "(غير معروف)"); + + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "يطلب التطبيق {app} صلاحية غير معروفة."); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "يحاول التطبيق {app} التقاط الشاشة.\n\nهل تريد السماح له بذلك؟"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "يحاول التطبيق {app} تحميل إضافة: {plugin}.\n\nهل تريد السماح له بذلك؟"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "تم اكتشاف لوحة مفاتيح جديدة: {keyboard}.\n\nهل تريد السماح لها بالعمل؟"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(غير معروف)"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_TITLE, "طلب الإذن"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "تلميح: يمكنك تعيين قواعد دائمة لهذه الطلبات في ملف إعدادات Hyprland."); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_ALLOW, "السماح"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "السماح مع تذكّر الاختيار"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_ALLOW_ONCE, "السماح لمرة واحدة"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_DENY, "الرفض"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "تطبيق غير معروف (معرّف عميل Wayland {wayland_id})"); + + huEngine->registerEntry("ar", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "يبدو أنّ متغيّر البيئة XDG_CURRENT_DESKTOP يُدار من خارج النظام، والقيمة الحالية هي {value}.\n" + "قد يؤدي ذلك إلى مشكلات ما لم يكن مقصودًا."); + + huEngine->registerEntry("ar", TXT_KEY_NOTIF_NO_GUIUTILS, "لا يحتوي نظامك على الحزمة hyprland-guiutils مثبتة. هذه حزمة مطلوبة أثناء التشغيل لبعض مربعات الحوار. يُنصَح بتثبيتها."); + + huEngine->registerEntry("ar", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "فشل Hyprland في تحميل مورد أساسي ({count}). قد يكون السبب سوء تغليف الحزم في التوزيعة."; + return "فشل Hyprland في تحميل {count} من الموارد الأساسية. قد يكون السبب سوء تغليف الحزم في التوزيعة."; + }); + + huEngine->registerEntry("ar", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "تم إعداد مخطط الشاشات لديك بشكل غير صحيح. الشاشة {name} تتداخل مع شاشة أو أكثر في المخطط.\n" + "يرجى مراجعة صفحة الشاشات في الويكي لمزيد من التفاصيل. هذا سيسبب مشكلات."); + + huEngine->registerEntry("ar", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "فشلت الشاشة {name} في ضبط أي من الأوضاع المطلوبة، وسيتم الرجوع إلى الوضع {mode}."); + + huEngine->registerEntry("ar", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "تم تمرير قيمة تحجيم غير صالحة إلى الشاشة {name}: {scale}. سيتم استخدام قيمة التحجيم المقترحة: {fixed_scale}."); + + huEngine->registerEntry("ar", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "فشل تحميل الإضافة {name}: {error}"); + + huEngine->registerEntry("ar", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "فشلت إعادة تحميل نظام إدارة الألوان (CM). سيتم الرجوع إلى صيغة الألوان rgba/rgbx."); + + huEngine->registerEntry("ar", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "الشاشة {name}: تم تفعيل نطاق الألوان الواسع، لكن العرض ليس في وضع 10 بت."); + // ru_RU (Russian) huEngine->registerEntry("ru_RU", TXT_KEY_ANR_TITLE, "Приложение не отвечает"); huEngine->registerEntry("ru_RU", TXT_KEY_ANR_CONTENT, "Приложение {title} - {class} не отвечает.\nЧто вы хотите сделать?"); From dfb4dcd55c76ad9eca7c66fc0dd11cd8ebd2f2e2 Mon Sep 17 00:00:00 2001 From: Antarip Barman <47138518+sadbytes@users.noreply.github.com> Date: Mon, 17 Nov 2025 05:28:43 +0530 Subject: [PATCH 30/56] i18n: add Assamese translations (#12356) --- src/i18n/Engine.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 7fdcec195..d0f2567ff 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -99,6 +99,43 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("en_US", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader reload failed, falling back to rgba/rgbx."); huEngine->registerEntry("en_US", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: wide color gamut is enabled but the display is not in 10-bit mode."); + // as_IN (Assamese) + huEngine->registerEntry("as_IN", TXT_KEY_ANR_TITLE, "এপ্লিকেচনে উত্তৰ দিয়া নাই"); + huEngine->registerEntry("as_IN", TXT_KEY_ANR_CONTENT, "এপ্লিকেচন {title} - {class}-এ উত্তৰ দিয়া নাই।\nআপুনি এয়াৰ লগত কি কৰিব বিচাৰে?"); + huEngine->registerEntry("as_IN", TXT_KEY_ANR_OPTION_TERMINATE, "সমাপ্ত কৰক"); + huEngine->registerEntry("as_IN", TXT_KEY_ANR_OPTION_WAIT, "অপেক্ষা কৰক"); + huEngine->registerEntry("as_IN", TXT_KEY_ANR_PROP_UNKNOWN, "(অজ্ঞাত)"); + + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "এপ্লিকেচন {app}-এ এটা অজ্ঞাত অনুমতি বিচাৰিছে।"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "এটা এপ্লিকেচন {app}-এ আপোনাৰ স্ক্ৰীণ কেপচাৰ কৰিবলৈ চেষ্টা কৰিছে।\n\nআপুনি ইয়াক অনুমতি দিব বিচাৰেনে?"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "এপ্লিকেচন {app}-এ এটা প্লাগিন লোড কৰিবলৈ চেষ্টা কৰিছে: {plugin}।\n\nআপুনি ইয়াক অনুমতি দিব বিচাৰেনে?"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "এটা নতুন কিবৰ্ড ধৰা পৰিছে: {keyboard}।\n\nআপুনি ইয়াক চলাবলৈ অনুমতি দিব বিচাৰেনে?"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(অজ্ঞাত)"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_TITLE, "অনুমতিৰ অনুৰোধ"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "ইঙ্গিত: আপুনি হাইপাৰলেণ্ড কনফিগ ফাইলত এইবোৰৰ বাবে স্থায়ী নিয়ম স্থাপন কৰিব পাৰে।"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_ALLOW, "অনুমতি দিয়ক"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "অনুমতি দি মনত ৰাখক"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_ALLOW_ONCE, "এবাৰ অনুমতি দিয়ক"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_DENY, "অস্বীকাৰ কৰক"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "অজ্ঞাত এপ্লিকেচন (ৱেইলেণ্ড ক্লায়েণ্ট আইডি {wayland_id})"); + + huEngine->registerEntry( + "as_IN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "আপোনাৰ XDG_CURRENT_DESKTOP পৰিৱেশটো বাহ্যিকভাৱে পৰিচালিত হোৱা যেন লাগিছে, আৰু বৰ্তমানৰ মান হৈছে {value}।\nযদি ই ইচ্ছাকৃতভাৱে নহয়, তেনে হলে সমস্যাৰ সৃষ্টি হ'ব পাৰে।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_NO_GUIUTILS, + "আপোনাৰ চিষ্টেমত hyprland-guiutils ইনষ্টল কৰা নাই। কিছুমান ডাইলগৰ বাবে ই এটা ৰানটাইম নিৰ্ভৰশীলতা। ইয়াক ইনষ্টল কৰাৰ কথা চিন্তা কৰক।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_FAILED_ASSETS, + "হাইপাৰলেণ্ড {count}-টা প্ৰয়োজনীয় সম্পদ লোড কৰাত অসফল হৈছে, বেয়া পেকজিং কৰাৰ বাবে আপোনাৰ ডিষ্ট্ৰ'ৰ পেকেজাৰক দোষাৰোপ কৰক!"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "আপোনাৰ মনিটৰৰ লেআউট ভুলকৈ ছেট কৰা হৈছে। মনিটৰ {name} লেআউটত আন মনিটৰ(সমূহ)ৰ সৈতে ওপৰা-উপৰি হৈ আছে।\nঅধিক তথ্যৰ বাবে অনুগ্ৰহ কৰি ৱিকি (মনিটৰ পৃষ্ঠা) চাওক। ই " + "সমস্যাৰ সৃষ্টি কৰিব।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "মনিটৰ {name}-এ কোনো অনুৰোধ কৰা মোড ছেট কৰাত অসফল হৈছে, মোড {mode}-লৈ ঘূৰি আহিছে।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "মনিটৰ {name}: {scale}-লৈ অবৈধ মাপন দিয়া হৈছে, পৰামৰ্শ দিয়া মাপন ব্যৱহাৰ কৰা যাব: {fixed_scale}"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "প্লাগিন {name} লোড কৰাত অসফল হৈছে: {error}"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM শ্বেডাৰ ৰিলোড কৰাত অসফল হৈছে, rgba/rgbx-লৈ ঘূৰি আহিছে।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "প্ৰসাৰিত ৰঙৰ বৰ্গ সক্ষম কৰা হৈছে কিন্তু ডিচপ্লে 10-বিট মোডত নাই।"); + // de_DE (German) huEngine->registerEntry("de_DE", TXT_KEY_ANR_TITLE, "Anwendung Reagiert Nicht"); huEngine->registerEntry("de_DE", TXT_KEY_ANR_CONTENT, "Eine Anwendung {title} - {class} reagiert nicht.\nWas möchten Sie damit tun?"); From 5265fa3be87f361b8f581d2f8361a0f7c4113806 Mon Sep 17 00:00:00 2001 From: Darkiu1337 <88014739+Darkiu1337@users.noreply.github.com> Date: Sun, 16 Nov 2025 20:58:54 -0300 Subject: [PATCH 31/56] i18n: add pt_BR translations (#12351) --- src/i18n/Engine.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index d0f2567ff..a18b6b7e8 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -215,6 +215,48 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader hed ned chönne neu glade wärde, es werd uf rgba/rgbx zrogggfalle."); huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Beldscherm {name}: wide color gamut esch aktiviert aber de Beldscherm esch ned im 10-bit Modus."); + // pt_BR (Brazilian Portuguese) + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_TITLE, "O aplicativo não está respondendo"); + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_CONTENT, "O aplicativo {title} - {class} não está respondendo.\nO que você deseja fazer?"); + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_OPTION_TERMINATE, "Encerrar"); + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_OPTION_WAIT, "Esperar"); + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_PROP_UNKNOWN, "(Desconhecido)"); + + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "O aplicativo {app} está pedindo uma permissão desconhecida."); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "O aplicativo {app} está tentando capturar sua tela.\n\nVocê deseja permitir?"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "O aplicativo {app} está tentando carregar um plugin: {plugin}.\n\nVocê deseja permitir?"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Um novo teclado foi detectado: {keyboard}.\n\nVocê deseja permitir seu uso?"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(Desconhecido)"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_TITLE, "Solicitação de permissão"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, + "Dica: você pode definir regras persistentes para essas permissões no arquivo de configuração do Hyprland"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_ALLOW, "Permitir"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Permitir e lembrar"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_ALLOW_ONCE, "Permitir uma vez"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_DENY, "Negar"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Aplicativo desconhecido (wayland client ID {wayland_id})"); + + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Seu XDG_CURRENT_DESKTOP parece estar sendo gerenciado externamente, e atualmente é {value}.\nIsso pode causar problemas caso não seja intencional."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_NO_GUIUTILS, + "Seu sistema não possui hyprland-guiutils instalado. Essa é uma dependência de execução para alguns diálogos. Considere instalá-lo."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "O Hyprland falhou ao carregar {count} recurso essencial, culpe o empacotador da sua distro por fazer um péssimo trabalho!"; + return "O Hyprland falhou ao carregar {count} recursos essenciais, culpe o empacotador da sua distro por fazer um péssimo trabalho!"; + }); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Sua disposição de monitores está configurada incorretamente. O monitor {name} se sobrepõe a outro(s) monitor(es) na disposição.\nPor favor consulte " + "a wiki (Monitors page) para " + "mais informações. Isso vai causar problemas."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "O monitor {name} falhou em definir qualquer um dos modos solicitados, voltando ao modo {mode}."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Um fator de escala inválido foi passado para o monitor {name}: {scale}, usando o fator sugerido: {fixed_scale}"); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Falha ao carregar o plugin {name}: {error}"); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Falha ao carregar o shader CM, voltando para rgba/rgbx."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: o modo de gama de cores amplo está ativado, mas a tela não está configurada para 10 bits."); + // es (Spanish) huEngine->registerEntry("es", TXT_KEY_ANR_TITLE, "La aplicación no responde"); huEngine->registerEntry("es", TXT_KEY_ANR_CONTENT, "Una aplicación {title} - {class} no responde.\n¿Qué quieres hacer?"); From 9d67511871e0c1fb56564bcdee02f57c3ef31039 Mon Sep 17 00:00:00 2001 From: nnra <104775644+nnra6864@users.noreply.github.com> Date: Mon, 17 Nov 2025 00:59:05 +0100 Subject: [PATCH 32/56] i18n: add Serbian Translations (#12341) --- src/i18n/Engine.cpp | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index a18b6b7e8..53fb2ebc3 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -638,6 +638,90 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Не удалось перезагрузить CM shader, используется rgba/rgbx."); huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монитор {name}: расширенный цветовой охват включён, но дисплей не в 10-bit режиме."); + // sr_RS (Serbian) + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_TITLE, "Апликација не реагује"); + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_CONTENT, "Апликација {title} - {class} не реагује.\nШта желите да урадите са њом?"); + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_OPTION_TERMINATE, "Прекини"); + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_OPTION_WAIT, "Чекај"); + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_PROP_UNKNOWN, "(непознато)"); + + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Апликација {app} захтева непознату дозволу."); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Апликација {app} покушава да снима твој екран.\n\nДа ли желиш да то дозволиш?"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Апликација {app} покушава да учита додатак: {plugin}.\n\nДа ли желиш да то дозволиш?"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Нова тастатура је детектована: {keyboard}.\n\nДа ли желиш да дозволиш њен рад?"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(непознато)"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_TITLE, "Захтев за дозволу"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Савет: можеш направити трајна правила за ово у Hyprland конфигурационој датотеци."); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_ALLOW, "Дозволи"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Дозволи и запамти"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_ALLOW_ONCE, "Дозволи једном"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_DENY, "Одбиј"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Непозната апликација (wayland client ID {wayland_id})"); + + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Изгледа да се твојим XDG_CURRENT_DESKTOP окружењем управља споља, и тренутна вредност је {value}.\nОво може правити проблеме осим ако је намерно."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_NO_GUIUTILS, + "Твој систем нема инсталиран hyprland-guiutils. Ово је зависност при покретању за неке дијалоге. Размотри инсталацију."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland није успео да учита {count} кључни ресурс, криви пакера твоје дистрибуције за лоше одрађен посао!"; + if (assetsNo <= 4) + return "Hyprland није успео да учита {count} кључна ресурса, криви пакера твоје дистрибуције за лоше одрађен посао!"; + return "Hyprland није успео да учита {count} кључних ресурса, криви пакера твоје дистрибуције за лоше одрађен посао!"; + }); + huEngine->registerEntry( + "sr_RS", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Твој распоред монитора је неправилно постављен. Монитор {name} се преклапа са другим монитором/мониторима у распореду.\nМолим те погледај вики (Monitors страницу) за " + "више информација. Ово ће изазвати проблеме."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Монитор {name} није успео да постави ниједан тражени режим, враћање на режим {mode}."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Невалидна скала прослеђена монитору {name}: {scale}, користи се препоручена скала: {fixed_scale}"); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Неуспешно учитавање додатка {name}: {error}"); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Поново учитавање CM шејдера није успело, враћање на rgba/rgbx."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монитор {name}: широк спектар боја је омогућен али екран није у 10-битном режиму."); + + // sr_RS@latin (Serbian Latin) + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_TITLE, "Aplikacija ne reaguje"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_CONTENT, "Aplikacija {title} - {class} ne reaguje.\nŠta želite da uradite sa njom?"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_OPTION_TERMINATE, "Prekini"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_OPTION_WAIT, "Čekaj"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_PROP_UNKNOWN, "(nepoznato)"); + + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Aplikacija {app} zahteva nepoznatu dozvolu."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Aplikacija {app} pokušava da snima tvoj ekran.\n\nDa li želiš da to dozvoliš?"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Aplikacija {app} pokušava da učita dodatak: {plugin}.\n\nDa li želiš da to dozvoliš?"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Nova tastatura je detektovana: {keyboard}.\n\nDa li želiš da dozvoliš njen rad?"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(nepoznato)"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_TITLE, "Zahtev za dozvolu"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Savet: možeš napraviti trajna pravila za ovo u Hyprland konfiguracionoj datoteci."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_ALLOW, "Dozvoli"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Dozvoli i zapamti"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_ALLOW_ONCE, "Dozvoli jednom"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_DENY, "Odbij"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Nepoznata aplikacija (wayland client ID {wayland_id})"); + + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Izgleda da se tvojim XDG_CURRENT_DESKTOP okruženjem upravlja spolja, i trenutna vrednost je {value}.\nOvo može praviti probleme osim ako je namerno."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_NO_GUIUTILS, + "Tvoj sistem nema instaliran hyprland-guiutils. Ovo je zavisnost pri pokretanju za neke dijaloge. Razmotri instalaciju."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland nije uspeo da učita {count} ključni resurs, krivi pakera tvoje distribucije za loše odrađen posao!"; + if (assetsNo <= 4) + return "Hyprland nije uspeo da učita {count} ključna resursa, krivi pakera tvoje distribucije za loše odrađen posao!"; + return "Hyprland nije uspeo da učita {count} ključnih resursa, krivi pakera tvoje distribucije za loše odrađen posao!"; + }); + huEngine->registerEntry( + "sr_RS@latin", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Tvoj raspored monitora je nepravilno postavljen. Monitor {name} se preklapa sa drugim monitorom/monitorima u rasporedu.\nMolim te pogledaj wiki (Monitors stranicu) za " + "više informacija. Ovo će izazvati probleme."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} nije uspeo da postavi nijedan traženi režim, vraćanje na režim {mode}."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Nevalidna skala prosleđena monitoru {name}: {scale}, koristi se preporučena skala: {fixed_scale}"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Neuspešno učitavanje dodatka {name}: {error}"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Ponovno učitavanje CM šejdera nije uspelo, vraćanje na rgba/rgbx."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: širok spektar boja je omogućen ali ekran nije u 10-bitnom režimu."); + // tr_TR (Turkish) huEngine->registerEntry("tr_TR", TXT_KEY_ANR_TITLE, "Uygulama Yanıt Vermiyor"); huEngine->registerEntry("tr_TR", TXT_KEY_ANR_CONTENT, "Bir uygulama {title} - {class} yanıt vermiyor.\nBununla ne yapmak istiyorsun?"); From cefa63c2af71045d14b65234ae0577533e447aeb Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Sun, 16 Nov 2025 20:44:22 +0200 Subject: [PATCH 33/56] meson: drop --- CMakeLists.txt | 1 - assets/install/meson.build | 10 --- assets/meson.build | 7 -- docs/meson.build | 2 - example/meson.build | 10 --- hyprctl/meson.build | 27 ------- hyprpm/src/meson.build | 32 -------- meson.build | 151 ------------------------------------- meson_options.txt | 5 -- protocols/meson.build | 119 ----------------------------- src/meson.build | 58 -------------- subprojects/tracy.wrap | 1 - subprojects/udis86.wrap | 5 -- systemd/meson.build | 7 -- 14 files changed, 435 deletions(-) delete mode 100644 assets/install/meson.build delete mode 100644 assets/meson.build delete mode 100644 docs/meson.build delete mode 100644 example/meson.build delete mode 100644 hyprctl/meson.build delete mode 100644 hyprpm/src/meson.build delete mode 100644 meson.build delete mode 100644 meson_options.txt delete mode 100644 protocols/meson.build delete mode 100644 src/meson.build delete mode 100644 subprojects/tracy.wrap delete mode 100644 subprojects/udis86.wrap delete mode 100644 systemd/meson.build diff --git a/CMakeLists.txt b/CMakeLists.txt index f641813ea..014f386ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -511,7 +511,6 @@ add_compile_definitions(DATAROOTDIR="${CMAKE_INSTALL_FULL_DATAROOTDIR}") # installable assets file(GLOB_RECURSE INSTALLABLE_ASSETS "assets/install/*") -list(FILTER INSTALLABLE_ASSETS EXCLUDE REGEX "meson.build") install(FILES ${INSTALLABLE_ASSETS} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr) diff --git a/assets/install/meson.build b/assets/install/meson.build deleted file mode 100644 index 450764695..000000000 --- a/assets/install/meson.build +++ /dev/null @@ -1,10 +0,0 @@ -globber = run_command('sh', '-c', 'find . -type f -not -name "*.build"', check: true) -files = globber.stdout().strip().split('\n') - -foreach file : files - install_data( - file, - install_dir: join_paths(get_option('datadir'), 'hypr'), - install_tag: 'runtime', - ) -endforeach diff --git a/assets/meson.build b/assets/meson.build deleted file mode 100644 index 2a28121dd..000000000 --- a/assets/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -install_data( - 'hyprland-portals.conf', - install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal'), - install_tag: 'runtime', -) - -subdir('install') diff --git a/docs/meson.build b/docs/meson.build deleted file mode 100644 index 6ff51d1a5..000000000 --- a/docs/meson.build +++ /dev/null @@ -1,2 +0,0 @@ -install_man('Hyprland.1') -install_man('hyprctl.1') diff --git a/example/meson.build b/example/meson.build deleted file mode 100644 index a338644e0..000000000 --- a/example/meson.build +++ /dev/null @@ -1,10 +0,0 @@ -install_data( - 'hyprland.conf', - install_dir: join_paths(get_option('datadir'), 'hypr'), - install_tag: 'runtime', -) -install_data( - 'hyprland.desktop', - install_dir: join_paths(get_option('datadir'), 'wayland-sessions'), - install_tag: 'runtime', -) diff --git a/hyprctl/meson.build b/hyprctl/meson.build deleted file mode 100644 index d6769b84a..000000000 --- a/hyprctl/meson.build +++ /dev/null @@ -1,27 +0,0 @@ -executable( - 'hyprctl', - 'main.cpp', - dependencies: [ - dependency('hyprutils', version: '>= 0.1.1'), - dependency('re2', required: true) - ], - install: true, -) - -install_data( - 'hyprctl.bash', - install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'), - install_tag: 'runtime', - rename: 'hyprctl', -) -install_data( - 'hyprctl.fish', - install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'), - install_tag: 'runtime', -) -install_data( - 'hyprctl.zsh', - install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'), - install_tag: 'runtime', - rename: '_hyprctl', -) diff --git a/hyprpm/src/meson.build b/hyprpm/src/meson.build deleted file mode 100644 index fd914f9d2..000000000 --- a/hyprpm/src/meson.build +++ /dev/null @@ -1,32 +0,0 @@ -globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true) -src = globber.stdout().strip().split('\n') - -executable( - 'hyprpm', - src, - dependencies: [ - dependency('hyprutils', version: '>= 0.1.1'), - dependency('threads'), - dependency('tomlplusplus'), - dependency('glaze', method: 'cmake'), - ], - install: true, -) - -install_data( - '../hyprpm.bash', - install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'), - install_tag: 'runtime', - rename: 'hyprpm', -) -install_data( - '../hyprpm.fish', - install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'), - install_tag: 'runtime', -) -install_data( - '../hyprpm.zsh', - install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'), - install_tag: 'runtime', - rename: '_hyprpm', -) diff --git a/meson.build b/meson.build deleted file mode 100644 index e1e2df949..000000000 --- a/meson.build +++ /dev/null @@ -1,151 +0,0 @@ -project( - 'Hyprland', - 'cpp', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'VERSION'), check: true).stdout().strip(), - default_options: [ - 'warning_level=2', - 'default_library=static', - 'optimization=3', - 'buildtype=release', - 'debug=false', - 'b_lto=false', - 'cpp_std=c++26', - ], - meson_version: '>= 1.1.0', -) - -datarootdir = '-DDATAROOTDIR="' + get_option('prefix') / get_option('datadir') + '"' -add_project_arguments( - [ - '-Wno-unused-parameter', - '-Wno-unused-value', - '-Wno-missing-field-initializers', - '-Wno-narrowing', - '-Wno-pointer-arith', - datarootdir, - '-DHYPRLAND_VERSION="' + meson.project_version() + '"', - ], - language: 'cpp', -) - -cpp_compiler = meson.get_compiler('cpp') -if cpp_compiler.check_header('execinfo.h') - add_project_arguments('-DHAS_EXECINFO', language: 'cpp') -endif - -aquamarine = dependency('aquamarine', version: '>=0.9.3') -hyprcursor = dependency('hyprcursor', version: '>=0.1.7') -hyprgraphics = dependency('hyprgraphics', version: '>=0.1.6') -hyprlang = dependency('hyprlang', version: '>=0.3.2') -hyprutils = dependency('hyprutils', version: '>=0.8.2') - -aq_ver_list = aquamarine.version().split('.') -git = find_program('git', required: false) - -if git.found() - git_hash = run_command(git, 'rev-parse', 'HEAD').stdout().strip() - git_branch = run_command(git, 'branch', '--show-current').stdout().strip() - git_message = run_command(git, 'show', '-s', '--format=%s', '--no-show-signature').stdout().strip() - git_date = run_command(git, 'show', '-s', '--format=%cd', '--date=local', '--no-show-signature').stdout().strip() - git_dirty = run_command(git, 'diff-index', '--quiet', 'HEAD', '--', check: false).returncode() != 0 ? 'dirty' : 'clean' - git_tag = run_command(git, 'describe', '--tags').stdout().strip() - git_commits = run_command(git, 'rev-list', '--count', 'HEAD').stdout().strip() -else - git_hash = 'unknown' - git_branch = 'unknown' - git_message = 'unknown' - git_date = 'unknown' - git_dirty = 'unknown' - git_tag = 'unknown' - git_commits = '0' -endif - -cfg = configuration_data() -cfg.set('GIT_COMMIT_HASH', git_hash) -cfg.set('GIT_BRANCH', git_branch) -cfg.set('GIT_COMMIT_MESSAGE', git_message) -cfg.set('GIT_COMMIT_DATE', git_date) -cfg.set('GIT_DIRTY', git_dirty) -cfg.set('GIT_TAG', git_tag) -cfg.set('GIT_COMMITS', git_commits) -cfg.set('AQUAMARINE_VERSION', aquamarine.version()) -cfg.set('AQUAMARINE_VERSION_MAJOR', aq_ver_list[0]) -cfg.set('AQUAMARINE_VERSION_MINOR', aq_ver_list[1]) -cfg.set('AQUAMARINE_VERSION_PATCH', aq_ver_list[2]) -cfg.set('HYPRLANG_VERSION', hyprlang.version()) -cfg.set('HYPRUTILS_VERSION', hyprutils.version()) -cfg.set('HYPRCURSOR_VERSION', hyprcursor.version()) -cfg.set('HYPRGRAPHICS_VERSION', hyprgraphics.version()) - -version_h = configure_file( - input: 'src/version.h.in', - output: 'version.h', - configuration: cfg -) - -install_headers(version_h, subdir: 'hyprland/src') - -xcb_dep = dependency('xcb', required: get_option('xwayland')) -xcb_composite_dep = dependency('xcb-composite', required: get_option('xwayland')) -xcb_errors_dep = dependency('xcb-errors', required: get_option('xwayland')) -xcb_icccm_dep = dependency('xcb-icccm', required: get_option('xwayland')) -xcb_render_dep = dependency('xcb-render', required: get_option('xwayland')) -xcb_res_dep = dependency('xcb-res', required: get_option('xwayland')) -xcb_xfixes_dep = dependency('xcb-xfixes', required: get_option('xwayland')) -gio_dep = dependency('gio-2.0', required: true) - -if not xcb_dep.found() - add_project_arguments('-DNO_XWAYLAND', language: 'cpp') -endif - -backtrace_dep = cpp_compiler.find_library('execinfo', required: false) -epoll_dep = dependency('epoll-shim', required: false) -inotify_dep = dependency('libinotify', required: false) -re2 = dependency('re2', required: true) - -systemd_option = get_option('systemd') -systemd = dependency('systemd', required: systemd_option) -systemd_option.enable_auto_if(systemd.found()) -if (systemd_option.enabled()) - add_project_arguments('-DUSES_SYSTEMD', language: 'cpp') - subdir('systemd') -endif - -if get_option('buildtype') == 'debug' - add_project_arguments('-DHYPRLAND_DEBUG', language: 'cpp') -endif - -run_command('sh', '-c', 'scripts/generateShaderIncludes.sh', check: true) - -globber = run_command('find', 'src', '-name', '*.h*', '-o', '-name', '*.inc', check: true) -headers = globber.stdout().strip().split('\n') -foreach file : headers - install_headers(file, subdir: 'hyprland', preserve_path: true) -endforeach -install_headers(version_h, subdir: 'src') - -tracy = dependency('tracy', static: true, required: get_option('tracy_enable')) -if get_option('tracy_enable') and get_option('buildtype') != 'debugoptimized' - warning('Profiling builds should set -- buildtype = debugoptimized') -endif - -subdir('protocols') -subdir('src') -subdir('hyprctl') -subdir('assets') -subdir('example') -subdir('docs') -if get_option('hyprpm').enabled() - subdir('hyprpm/src') -endif - -pkg_install_dir = join_paths(get_option('datadir'), 'pkgconfig') -import('pkgconfig').generate( - name: 'Hyprland', - filebase: 'hyprland', - url: 'https://github.com/hyprwm/Hyprland', - description: 'Hyprland header files', - install_dir: pkg_install_dir, - subdirs: ['', 'hyprland/protocols', 'hyprland'], -) diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index e50b4ccee..000000000 --- a/meson_options.txt +++ /dev/null @@ -1,5 +0,0 @@ -option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') -option('systemd', type: 'feature', value: 'auto', description: 'Enable systemd integration') -option('uwsm', type: 'feature', value: 'enabled', description: 'Enable uwsm integration (only if systemd is enabled)') -option('hyprpm', type: 'feature', value: 'enabled', description: 'Enable hyprpm') -option('tracy_enable', type: 'boolean', value: false , description: 'Enable profiling') diff --git a/protocols/meson.build b/protocols/meson.build deleted file mode 100644 index 33663fa38..000000000 --- a/protocols/meson.build +++ /dev/null @@ -1,119 +0,0 @@ -wayland_protos = dependency( - 'wayland-protocols', - version: '>=1.45', - fallback: 'wayland-protocols', - default_options: ['tests=false'], -) - -hyprland_protos = dependency( - 'hyprland-protocols', - version: '>=0.6.4', - fallback: 'hyprland-protocols', -) - -wayland_protocol_dir = wayland_protos.get_variable('pkgdatadir') -hyprland_protocol_dir = hyprland_protos.get_variable('pkgdatadir') - -hyprwayland_scanner_dep = dependency('hyprwayland-scanner', version: '>=0.3.10', native: true) -hyprwayland_scanner = find_program( - hyprwayland_scanner_dep.get_variable('hyprwayland_scanner'), - native: true, -) - -protocols = [ - 'wlr-gamma-control-unstable-v1.xml', - 'wlr-foreign-toplevel-management-unstable-v1.xml', - 'wlr-output-power-management-unstable-v1.xml', - 'input-method-unstable-v2.xml', - 'virtual-keyboard-unstable-v1.xml', - 'wlr-virtual-pointer-unstable-v1.xml', - 'wlr-output-management-unstable-v1.xml', - 'kde-server-decoration.xml', - 'wlr-layer-shell-unstable-v1.xml', - 'wayland-drm.xml', - 'wlr-data-control-unstable-v1.xml', - 'wlr-screencopy-unstable-v1.xml', - 'xx-color-management-v4.xml', - 'frog-color-management-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-toplevel-mapping-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-focus-grab-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-ctm-control-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-surface-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-lock-notify-v1.xml', - wayland_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', - wayland_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', - wayland_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', - wayland_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', - wayland_protocol_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml', - wayland_protocol_dir / 'unstable/relative-pointer/relative-pointer-unstable-v1.xml', - wayland_protocol_dir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml', - wayland_protocol_dir / 'staging/alpha-modifier/alpha-modifier-v1.xml', - wayland_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', - wayland_protocol_dir / 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml', - wayland_protocol_dir / 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml', - wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v3.xml', - wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v1.xml', - wayland_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', - wayland_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', - wayland_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', - wayland_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', - wayland_protocol_dir / 'stable/tablet/tablet-v2.xml', - wayland_protocol_dir / 'stable/presentation-time/presentation-time.xml', - wayland_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', - wayland_protocol_dir / 'unstable/primary-selection/primary-selection-unstable-v1.xml', - wayland_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', - wayland_protocol_dir / 'stable/viewporter/viewporter.xml', - wayland_protocol_dir / 'stable/linux-dmabuf/linux-dmabuf-v1.xml', - wayland_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', - wayland_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml', - wayland_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml', - wayland_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', - wayland_protocol_dir / 'staging/security-context/security-context-v1.xml', - wayland_protocol_dir / 'staging/content-type/content-type-v1.xml', - wayland_protocol_dir / 'staging/color-management/color-management-v1.xml', - wayland_protocol_dir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml', - wayland_protocol_dir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml', - wayland_protocol_dir / 'staging/ext-workspace/ext-workspace-v1.xml', - wayland_protocol_dir / 'staging/ext-data-control/ext-data-control-v1.xml', - wayland_protocol_dir / 'staging/pointer-warp/pointer-warp-v1.xml', - wayland_protocol_dir / 'staging/fifo/fifo-v1.xml', - wayland_protocol_dir / 'staging/commit-timing/commit-timing-v1.xml', -] - -wl_protocols = [] -foreach protocol : protocols - wl_protocols += custom_target( - protocol.underscorify(), - input: protocol, - install: true, - install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')], - output: ['@BASENAME@.cpp', '@BASENAME@.hpp'], - command: [hyprwayland_scanner, '@INPUT@', '@OUTDIR@'], - ) -endforeach - -# wayland.xml generation -wayland_scanner = dependency('wayland-scanner', native: true) -wayland_scanner_datadir = wayland_scanner.get_variable('pkgdatadir') - -wayland_xml = wayland_scanner_datadir / 'wayland.xml' -wayland_protocol = custom_target( - wayland_xml.underscorify(), - input: wayland_xml, - install: true, - install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')], - output: ['@BASENAME@.cpp', '@BASENAME@.hpp'], - command: [hyprwayland_scanner, '--wayland-enums', '@INPUT@', '@OUTDIR@'], -) - -lib_server_protos = static_library( - 'server_protos', - wl_protocols + wayland_protocol, -) - -server_protos = declare_dependency( - link_with: lib_server_protos, - sources: wl_protocols + wayland_protocol, -) diff --git a/src/meson.build b/src/meson.build deleted file mode 100644 index d0a1590e8..000000000 --- a/src/meson.build +++ /dev/null @@ -1,58 +0,0 @@ -globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true) -src = globber.stdout().strip().split('\n') - -executable( - 'Hyprland', - src, - link_args: '-rdynamic', - cpp_pch: 'pch/pch.hpp', - dependencies: [ - server_protos, - aquamarine, - hyprcursor, - hyprgraphics, - hyprlang, - hyprutils, - dependency('gbm'), - dependency('xcursor'), - dependency('wayland-server'), - dependency('wayland-client'), - dependency('cairo'), - dependency('libdrm'), - dependency('egl'), - dependency('xkbcommon'), - dependency('libinput', version: '>=1.28'), - dependency('re2'), - xcb_dep, - xcb_composite_dep, - xcb_errors_dep, - xcb_icccm_dep, - xcb_render_dep, - xcb_res_dep, - xcb_xfixes_dep, - backtrace_dep, - epoll_dep, - inotify_dep, - gio_dep, - tracy, - - # Try to find canihavesomecoffee's udis86 using pkgconfig - # vmt/udis86 does not provide a .pc file and won't be detected this way - # Falls back to using the subproject through udis86.wrap - dependency('udis86'), - - dependency('pixman-1'), - dependency('gl', 'opengl'), - dependency('threads'), - dependency('pango'), - dependency('pangocairo'), - dependency('uuid'), - ], - install: true, -) - -install_symlink( - 'hyprland', - install_dir: get_option('bindir'), - pointing_to: 'Hyprland', -) diff --git a/subprojects/tracy.wrap b/subprojects/tracy.wrap deleted file mode 100644 index 11b217878..000000000 --- a/subprojects/tracy.wrap +++ /dev/null @@ -1 +0,0 @@ -[wrap-file] diff --git a/subprojects/udis86.wrap b/subprojects/udis86.wrap deleted file mode 100644 index dfb639841..000000000 --- a/subprojects/udis86.wrap +++ /dev/null @@ -1,5 +0,0 @@ -[wrap-file] -method = cmake - -[provide] -udis86 = libudis86_dep diff --git a/systemd/meson.build b/systemd/meson.build deleted file mode 100644 index bc62e95ae..000000000 --- a/systemd/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -if (get_option('uwsm').allowed()) - install_data( - 'hyprland-uwsm.desktop', - install_dir: join_paths(get_option('datadir'), 'wayland-sessions'), - install_tag: 'runtime', - ) -endif From 484d87d4698654888bc392d9bbca784ff8312579 Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Sun, 16 Nov 2025 21:05:48 +0200 Subject: [PATCH 34/56] CI: drop meson build, simplify c-f check --- .github/workflows/ci.yaml | 39 ++++-------------------------- .github/workflows/clang-format.yml | 36 ++++++--------------------- 2 files changed, 13 insertions(+), 62 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d9c0d1a6c..2857c77ac 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,27 +41,6 @@ jobs: name: Build archive path: Hyprland.tar.xz - meson: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Build Hyprland with Meson (Arch)" - runs-on: ubuntu-latest - container: - image: archlinux - steps: - - name: Checkout repository actions - uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - - name: Setup base - uses: ./.github/actions/setup_base - - - name: Configure - run: meson setup build -Ddefault_library=static - - - name: Compile - run: ninja -C build - no-pch: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork name: "Build Hyprland without precompiled headers (Arch)" @@ -106,21 +85,13 @@ jobs: clang-format: permissions: read-all if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Code Style (Arch)" + name: "Code Style" runs-on: ubuntu-latest - container: - image: archlinux steps: - - name: Checkout repository actions + - name: Checkout repository uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - - name: Setup base - uses: ./.github/actions/setup_base - - - name: Configure - run: meson setup build -Ddefault_library=static - name: clang-format check - run: ninja -C build clang-format-check + uses: jidicula/clang-format-action@v4.16.0 + with: + exclude-regex: ^subprojects$ diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index e935a605b..505829e36 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -4,43 +4,23 @@ jobs: clang-format: permissions: write-all if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Code Style (Arch)" + name: "Code Style" runs-on: ubuntu-latest - container: - image: archlinux steps: - - name: Checkout repository actions + - name: Checkout repository uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - - name: Setup base - uses: ./.github/actions/setup_base - - - name: Configure - run: meson setup build -Ddefault_library=static - name: clang-format check - run: ninja -C build clang-format-check + uses: jidicula/clang-format-action@v4.16.0 + with: + exclude-regex: ^subprojects$ - - name: clang-format apply - if: ${{ failure() && github.event_name == 'pull_request' }} - run: ninja -C build clang-format - - - name: Create patch + - name: Create comment if: ${{ failure() && github.event_name == 'pull_request' }} run: | - echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style), or directly apply this patch:' > clang-format.patch - echo '
' >> clang-format.patch - echo 'clang-format.patch' >> clang-format.patch - echo >> clang-format.patch - echo '```diff' >> clang-format.patch - git diff >> clang-format.patch - echo '```' >> clang-format.patch - echo >> clang-format.patch - echo '
' >> clang-format.patch + echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style).' > clang-format.patch - - name: Comment patch + - name: Post comment if: ${{ failure() && github.event_name == 'pull_request' }} uses: mshick/add-pr-comment@v2 with: From 68c23fbdafad76bc42b4f7240351c0d5a41c170d Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Mon, 17 Nov 2025 08:56:24 +0200 Subject: [PATCH 35/56] CI: drop no_pch and make default, drop noxwayland --- .github/workflows/ci.yaml | 43 +-------------------------------------- 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2857c77ac..d14ac02e9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,7 +21,7 @@ jobs: - name: Build Hyprland run: | - CFLAGS=-Werror CXXFLAGS=-Werror make all + CFLAGS=-Werror CXXFLAGS=-Werror make nopch - name: Compress and package artifacts run: | @@ -41,47 +41,6 @@ jobs: name: Build archive path: Hyprland.tar.xz - no-pch: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Build Hyprland without precompiled headers (Arch)" - runs-on: ubuntu-latest - container: - image: archlinux - steps: - - name: Checkout repository actions - uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - - name: Setup base - uses: ./.github/actions/setup_base - with: - INSTALL_XORG_PKGS: true - - - name: Compile - run: make nopch - - noxwayland: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Build Hyprland in pure Wayland (Arch)" - runs-on: ubuntu-latest - container: - image: archlinux - steps: - - name: Checkout repository actions - uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - - name: Setup base - uses: ./.github/actions/setup_base - - - name: Configure - run: mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DNO_XWAYLAND:STRING=true -H./ -B./build -G Ninja - - - name: Compile - run: make release - clang-format: permissions: read-all if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork From 2b14f27ca87aab7e05dac00dd8534cb878236536 Mon Sep 17 00:00:00 2001 From: fufexan <36706276+fufexan@users.noreply.github.com> Date: Mon, 17 Nov 2025 07:59:34 +0000 Subject: [PATCH 36/56] [gha] Nix: update inputs --- flake.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index b9fb17867..5df06468d 100644 --- a/flake.lock +++ b/flake.lock @@ -193,11 +193,11 @@ ] }, "locked": { - "lastModified": 1758927902, - "narHash": "sha256-LZgMds7M94+vuMql2bERQ6LiFFdhgsEFezE4Vn+Ys3A=", + "lastModified": 1763254292, + "narHash": "sha256-JNgz3Fz2KMzkT7aR72wsgu/xNeJB//LSmdilh8Z/Zao=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "4dafa28d4f79877d67a7d1a654cddccf8ebf15da", + "rev": "deea98d5b61d066bdc7a68163edd2c4bd28d3a6b", "type": "github" }, "original": { @@ -261,11 +261,11 @@ ] }, "locked": { - "lastModified": 1762812168, - "narHash": "sha256-pY+dUqi2AYpH0HHT2JFzt1qWoJQBWtBdzzcL1ZK5Mwo=", + "lastModified": 1763323331, + "narHash": "sha256-+Z0OfCo1MS8/aIutSAW5aJR9zTae1wz9kcJYMgpwN6M=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "cb3e797fde5c748164eb70d9859336141136a166", + "rev": "0c6411851cc779d551edc89b83966696201611aa", "type": "github" }, "original": { @@ -299,11 +299,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1762363567, - "narHash": "sha256-YRqMDEtSMbitIMj+JLpheSz0pwEr0Rmy5mC7myl17xs=", + "lastModified": 1763283776, + "narHash": "sha256-Y7TDFPK4GlqrKrivOcsHG8xSGqQx3A6c+i7novT85Uk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ae814fd3904b621d8ab97418f1d0f2eb0d3716f4", + "rev": "50a96edd8d0db6cc8db57dab6bb6d6ee1f3dc49a", "type": "github" }, "original": { @@ -322,11 +322,11 @@ ] }, "locked": { - "lastModified": 1762441963, - "narHash": "sha256-j+rNQ119ffYUkYt2YYS6rnd6Jh/crMZmbqpkGLXaEt0=", + "lastModified": 1763319842, + "narHash": "sha256-YG19IyrTdnVn0l3DvcUYm85u3PaqBt6tI6VvolcuHnA=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "8e7576e79b88c16d7ee3bbd112c8d90070832885", + "rev": "7275fa67fbbb75891c16d9dee7d88e58aea2d761", "type": "github" }, "original": { From e354066945199a1d0cb9069b7ab313e4c01692a7 Mon Sep 17 00:00:00 2001 From: Jochim Date: Mon, 17 Nov 2025 13:13:29 +0100 Subject: [PATCH 37/56] groupbar: fix rounding logic for edge cases (#12366) --- .../decorations/CHyprGroupBarDecoration.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp index b4bf2b86a..dbf66b60b 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -172,12 +172,13 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { rectdata.round = *PROUNDING; rectdata.roundingPower = *PROUNDINGPOWER; if (*PROUNDONLYEDGES) { + rectdata.round = 0; + const double offset = *PROUNDING * 2; if (i == 0) { rectdata.round = *PROUNDING; rectdata.clipBox = rect; - rectdata.box = CBox{rect.pos(), Vector2D{rect.w + (*PROUNDING * 2), rect.h}}; + rectdata.box = CBox{rect.pos(), Vector2D{rect.w + offset, rect.h}}; } else if (i == barsToDraw - 1) { - double offset = *PROUNDING * 2; rectdata.round = *PROUNDING; rectdata.clipBox = rect; rectdata.box = CBox{rect.pos() - Vector2D{offset, 0.F}, Vector2D{rect.w + offset, rect.h}}; @@ -205,15 +206,16 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { data.round = *PGRADIENTROUNDING; data.roundingPower = *PGRADIENTROUNDINGPOWER; if (*PGRADIENTROUNDINGONLYEDGES) { + data.round = 0; + const double offset = *PGRADIENTROUNDING * 2; if (i == 0) { data.round = *PGRADIENTROUNDING; data.clipBox = rect; - data.box = CBox{rect.pos(), Vector2D{rect.w + (*PGRADIENTROUNDING * 2), rect.h}}; + data.box = CBox{rect.pos(), Vector2D{rect.w + offset, rect.h}}; } else if (i == barsToDraw - 1) { - double offset = *PGRADIENTROUNDING * 2; - data.round = *PGRADIENTROUNDING; - data.clipBox = rect; - data.box = CBox{rect.pos() - Vector2D{offset, 0.F}, Vector2D{rect.w + offset, rect.h}}; + data.round = *PGRADIENTROUNDING; + data.clipBox = rect; + data.box = CBox{rect.pos() - Vector2D{offset, 0.F}, Vector2D{rect.w + offset, rect.h}}; } } } From 1796dbcdc3afe36848f6daa05ea721ca077ea996 Mon Sep 17 00:00:00 2001 From: Kosa Matyas <39438549+kosa12@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:38:57 +0200 Subject: [PATCH 38/56] i18n: Add hungarian translations (#12346) --- src/i18n/Engine.cpp | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 53fb2ebc3..ae1aebdc5 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -468,6 +468,45 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CMシェーダーのリロード失敗、rgba/rgbxを使いました。"); huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "画面{name}:広い色域は設定していますけど、画面は10ビットモードに設定されていません。"); + // hu_HU (Hungarian) + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_TITLE, "Az alkalmazás nem válaszol"); + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_CONTENT, "A(z) {title} - {class} alkalmazás nem válaszol.\nMit szeretne tenni vele?"); + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_OPTION_TERMINATE, "Leállítás"); + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_OPTION_WAIT, "Várakozás"); + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_PROP_UNKNOWN, "(ismeretlen)"); + + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "A(z) {app} alkalmazás ismeretlen engedélyt kér."); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "A(z) {app} alkalmazás megpróbálja rögzíteni a képernyőjét.\n\nEngedélyezi?"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "A(z) {app} alkalmazás megpróbál egy bővítményt betölteni: {plugin}.\n\nEngedélyezi?"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Új billentyűzetet észleltünk: {keyboard}.\n\nEngedélyezi a használatát?"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(ismeretlen)"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_TITLE, "Engedélykérés"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tipp: Állandó szabályokat állíthat be a Hyprland konfigurációs fájlban."); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_ALLOW, "Engedélyezés"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Mindig engedélyez"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_ALLOW_ONCE, "Egyszeri engedélyezés"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_DENY, "Elutasítás"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Ismeretlen alkalmazás (wayland kliens ID {wayland_id})"); + + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Úgy tűnik, hogy az XDG_CURRENT_DESKTOP környezetet külsőleg kezelik, és a jelenlegi érték {value}.\nEz problémákat okozhat, hacsak nem szándékos."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_NO_GUIUTILS, + "A rendszerében nincs telepítve a hyprland-guiutils. Ez egy futásidejű függőség néhány párbeszédablakhoz. Fontolja meg a telepítését."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "A Hyprland nem tudta betölteni az 1 szükséges erőforrást. Kérjük, jelezze a hibát a disztribúció csomagolójának."; + return "A Hyprland nem tudott betölteni {count} szükséges erőforrást. Kérjük, jelezze a hibát a disztribúció csomagolójának."; + }); + huEngine->registerEntry( + "hu_HU", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "A monitor elrendezése helytelenül van beállítva. A(z) {name} monitor átfedi a többi monitort az elrendezésben.\nKérjük, további információkért tekintse meg a wikit " + "(Monitors oldal). Ez problémákat fog okozni."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "A(z) {name} monitor nem tudta beállítani a kért módokat, visszaáll a(z) {mode} módra."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Érvénytelen skálázás a(z) {name} monitorhoz: {scale}, a javasolt skálázás használata: {fixed_scale}"); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nem sikerült betölteni a(z) {name} bővítményt: {error}"); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "A CM shader újratöltése sikertelen, visszaáll rgba/rgbx-re."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: A széles színtartomány engedélyezve van, de a kijelző nem 10 bites módban van."); // ml_IN (Malayalam) huEngine->registerEntry("ml_IN", TXT_KEY_ANR_TITLE, "ആപ്ലിക്കേഷൻ പ്രതികരിക്കുന്നില്ല"); huEngine->registerEntry("ml_IN", TXT_KEY_ANR_CONTENT, "ആപ്ലിക്കേഷൻ {title} - {class} പ്രതികരിക്കുന്നില്ല.\nഇതിന് നിങ്ങൾ എന്ത് ചെയ്യാൻ ആഗ്രഹിക്കുന്നു?"); From 4695f85829b73610975d04e694d46c04481e1b79 Mon Sep 17 00:00:00 2001 From: 27Onion Nebell <57032603+onion108@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:39:06 +0800 Subject: [PATCH 39/56] i18n: add Simplified Chinese translations (#12332) --- src/i18n/Engine.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index ae1aebdc5..9175cdf56 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -591,6 +591,38 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Nie udało się przeładować shader'a CM, użyto rgba/rgbx."); huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: skonfigurowano szeroką głębię barw, ale monitor nie jest w trybie 10-bit."); + // zh_CN (Simplified Chinese) + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_TITLE, "应用程序未响应"); + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_CONTENT, "应用程序 {title} - {class} 未响应。\n你想要采取什么行动?"); + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_OPTION_TERMINATE, "终止"); + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_OPTION_WAIT, "等待"); + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_PROP_UNKNOWN, "(未知)"); + + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "应用程序 {app} 正在请求一个未知的权限。"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "应用程序 {app} 想要捕获你的屏幕。\n\n允许它这么做吗?"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "应用程序 {app} 想要加载插件: {plugin}。\n\n允许它这么做吗?"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "检测到新的键盘 {keyboard} 接入了。\n\n允许这个键盘操作你的系统吗?"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(未知)"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_TITLE, "权限请求"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "提示:你可以在Hyprland配置中为他们创建永久性的规则。"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_ALLOW, "允许"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "总是允许"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_ALLOW_ONCE, "允许一次"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_DENY, "阻止"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "未知的应用程序 (Wayland客户端ID {wayland_id})"); + + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "你的环境变量XDG_CURRENT_DESKTOP似乎被外部管理,且当前的值为{value}。如果你不是有意这么做,这可能会导致问题。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_NO_GUIUTILS, "你的系统似乎没有安装hyprland-guiutils。这是一个用于部分对话框的运行时依赖。请考虑安装。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_FAILED_ASSETS, "Hyprland无法加载{count}个重要资产,问问你发行版的打包者在打包个什么玩意!?"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "你的显示器没有被正确设置。显示器 {name} 和其他显示器的布局重叠了。请看wiki中的“显示器”一章获取更多信息。这导致问题。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "显示器 {name} 无法被设置为任何请求的模式,将使用 {mode} 兜底。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "显示器 {name} 被设置了非法的缩放:{scale},将使用建议的缩放:{fixed_scale}"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "无法加载插件 {name}:{error}"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "无法重新加载CM着色器,将使用rgba/rgbx兜底。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "显示器 {name}:宽色域被启用了,但是显示器并不在10-bit模式。"); + // ar (Arabic - Modern Standard) huEngine->registerEntry("ar", TXT_KEY_ANR_TITLE, "التطبيق لا يستجيب"); huEngine->registerEntry("ar", TXT_KEY_ANR_CONTENT, "التطبيق {title} - {class} لا يستجيب.\nما الذي تريد فعله؟"); From 5f0575737fbf45fc454cde2841c23691e1345413 Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Mon, 17 Nov 2025 14:17:26 +0200 Subject: [PATCH 40/56] CI/AI translate: only run on src/i18n --- .github/workflows/translation-ai-check.yml | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index 555432796..ba8243717 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -14,18 +14,10 @@ permissions: issues: write jobs: - review: - name: Review Translation + changes: + name: Check i18n changes if: ${{ github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }} runs-on: ubuntu-latest - env: - OPENAI_MODEL: gpt-5-mini - SYSTEM_PROMPT: | - You are a programmer and a translator. Your job is to review the attached patch for adding translation to a piece of software and make sure the submitted translation is not malicious, and that it makes sense. If the translation is not malicious, and doesn't contain obvious grammatical mistakes, say "Translation check OK". Otherwise, say "Translation check not ok" and list bad entries. - Examples of bad translations include obvious trolling (slurs, etc) or nonsense sentences. Meaningful improvements may be suggested, but if there are only minor improvements, just reply with "Translation check OK". Do not provide anything but the result and (if applicable) the bad entries or improvements. - - AI_PROMPT: Translation patch below. - steps: - name: Checkout source code uses: actions/checkout@v5 @@ -36,11 +28,21 @@ jobs: filters: | i18n: - 'src/i18n/**' + review: + name: Review Translation + needs: changes + if: ${{ needs.changes.outputs.backend == 'true' }} + runs-on: ubuntu-latest - - name: Stop if i18n not changed - if: steps.changes.outputs.i18n != 'true' - run: echo "No i18n changes in this PR; skipping." && exit 0 + env: + OPENAI_MODEL: gpt-5-mini + SYSTEM_PROMPT: | + You are a programmer and a translator. Your job is to review the attached patch for adding translation to a piece of software and make sure the submitted translation is not malicious, and that it makes sense. If the translation is not malicious, and doesn't contain obvious grammatical mistakes, say "Translation check OK". Otherwise, say "Translation check not ok" and list bad entries. + Examples of bad translations include obvious trolling (slurs, etc) or nonsense sentences. Meaningful improvements may be suggested, but if there are only minor improvements, just reply with "Translation check OK". Do not provide anything but the result and (if applicable) the bad entries or improvements. + AI_PROMPT: Translation patch below. + + steps: - name: Determine PR number id: pr run: | From 526aa1d020044c736c497842f2344c9455ad8b39 Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Mon, 17 Nov 2025 09:10:00 +0200 Subject: [PATCH 41/56] CI/Nix: simplify cache config --- .github/workflows/nix-test.yml | 16 ++-------------- .github/workflows/nix-update-inputs.yml | 20 ++++---------------- .github/workflows/nix.yml | 16 ++-------------- flake.nix | 1 - nix/default.nix | 9 +++++---- nix/overlays.nix | 7 ++++++- nix/tests/default.nix | 2 +- 7 files changed, 20 insertions(+), 51 deletions(-) diff --git a/.github/workflows/nix-test.yml b/.github/workflows/nix-test.yml index 086f0077c..68357093a 100644 --- a/.github/workflows/nix-test.yml +++ b/.github/workflows/nix-test.yml @@ -20,25 +20,13 @@ jobs: - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }} + # restore and save a cache using this key (per job) + primary-key: nix-${{ runner.os }}-${{ github.job }} # if there's no cache hit, restore a cache by this prefix restore-prefixes-first-match: nix-${{ runner.os }} # collect garbage until the Nix store size (in bytes) is at most this number # before trying to save a new cache - # 1G = 1073741824 gc-max-store-size-linux: 5G - # do purge caches - purge: true - # purge all versions of the cache - purge-prefixes: nix-${{ runner.os }} - # created more than this number of seconds ago - purge-created: 0 - # or, last accessed more than this number of seconds ago - # relative to the start of the `Post Restore and save Nix store` phase - purge-last-accessed: 0 - # except any version with the key that is the same as the `primary-key` - purge-primary-key: never - uses: cachix/cachix-action@v15 with: diff --git a/.github/workflows/nix-update-inputs.yml b/.github/workflows/nix-update-inputs.yml index c83e98801..a3084b270 100644 --- a/.github/workflows/nix-update-inputs.yml +++ b/.github/workflows/nix-update-inputs.yml @@ -27,25 +27,13 @@ jobs: - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + # restore and save a cache using this key (per job) + primary-key: nix-${{ runner.os }}-${{ github.job }} # if there's no cache hit, restore a cache by this prefix - restore-prefixes-first-match: nix-${{ runner.os }}- + restore-prefixes-first-match: nix-${{ runner.os }} # collect garbage until the Nix store size (in bytes) is at most this number # before trying to save a new cache - # 1G = 1073741824 - gc-max-store-size-linux: 1G - # do purge caches - purge: true - # purge all versions of the cache - purge-prefixes: nix-${{ runner.os }}- - # created more than this number of seconds ago - purge-created: 0 - # or, last accessed more than this number of seconds ago - # relative to the start of the `Post Restore and save Nix store` phase - purge-last-accessed: 0 - # except any version with the key that is the same as the `primary-key` - purge-primary-key: never + gc-max-store-size-linux: 5G - name: Update inputs run: nix/update-inputs.sh diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 1d514c507..b46b37953 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -25,25 +25,13 @@ jobs: - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }} + # restore and save a cache using this key (per job) + primary-key: nix-${{ runner.os }}-${{ github.job }} # if there's no cache hit, restore a cache by this prefix restore-prefixes-first-match: nix-${{ runner.os }} # collect garbage until the Nix store size (in bytes) is at most this number # before trying to save a new cache - # 1G = 1073741824 gc-max-store-size-linux: 5G - # do purge caches - purge: true - # purge all versions of the cache - purge-prefixes: nix-${{ runner.os }} - # created more than this number of seconds ago - purge-created: 0 - # or, last accessed more than this number of seconds ago - # relative to the start of the `Post Restore and save Nix store` phase - purge-last-accessed: 0 - # except any version with the key that is the same as the `primary-key` - purge-primary-key: never - uses: cachix/cachix-action@v15 with: diff --git a/flake.nix b/flake.nix index 5c58e26d0..6799144b4 100644 --- a/flake.nix +++ b/flake.nix @@ -151,7 +151,6 @@ (pkgsFor.${system}) # hyprland-packages hyprland - hyprland-with-hyprtester hyprland-unwrapped # hyprland-extras xdg-desktop-portal-hyprland diff --git a/nix/default.nix b/nix/default.nix index e69f22424..867b5b0c6 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -45,12 +45,12 @@ commit, revCount, date, - withHyprtester ? false, # deprecated flags enableNvidiaPatches ? false, nvidiaPatches ? false, hidpiXWayland ? false, legacyRenderer ? false, + withHyprtester ? false, }: let inherit (builtins) foldl' readFile; inherit (lib.asserts) assertMsg; @@ -70,6 +70,7 @@ in assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed."; assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland"; assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported."; + assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now."; customStdenv.mkDerivation (finalAttrs: { pname = "hyprland${optionalString debug "-debug"}"; inherit version; @@ -85,6 +86,7 @@ in ../assets/install ../hyprctl ../hyprland.pc.in + ../hyprtester ../LICENSE ../protocols ../src @@ -94,7 +96,6 @@ in (fs.fileFilter (file: file.hasExt "conf" || file.hasExt "desktop") ../example) (fs.fileFilter (file: file.hasExt "sh") ../scripts) (fs.fileFilter (file: file.name == "CMakeLists.txt") ../.) - (optional withHyprtester ../hyprtester) ])); }; @@ -189,7 +190,7 @@ in "NO_UWSM" = true; "NO_HYPRPM" = true; "TRACY_ENABLE" = false; - "BUILD_HYPRTESTER" = withHyprtester; + "BUILD_HYPRTESTER" = true; }; preConfigure = '' @@ -208,7 +209,7 @@ in pkgconf ]} ''} - '' + optionalString withHyprtester '' + install hyprtester/pointer-warp -t $out/bin install hyprtester/pointer-scroll -t $out/bin ''; diff --git a/nix/overlays.nix b/nix/overlays.nix index 7f6bf2ae3..c7ef95b86 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -43,7 +43,12 @@ in { }; hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;}; - hyprland-with-hyprtester = final.hyprland.override {withHyprtester = true;}; + hyprland-with-hyprtester = + builtins.trace '' + hyprland-with-hyprtester was removed. Please use the hyprland package. + Hyprtester is always built now. + '' + final.hyprland; # deprecated packages hyprland-legacy-renderer = diff --git a/nix/tests/default.nix b/nix/tests/default.nix index d7c000617..ef92a4635 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -1,6 +1,6 @@ inputs: pkgs: let flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}; - hyprland = flake.hyprland-with-hyprtester; + hyprland = flake.hyprland; in { tests = pkgs.testers.runNixOSTest { name = "hyprland-tests"; From ad52ba9c1393de717f6b86b9e94c21be9e96e320 Mon Sep 17 00:00:00 2001 From: Martijn <26456798+dusmartijngames@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:34:57 +0100 Subject: [PATCH 42/56] i18n: Add Dutch translations (#12326) --- src/i18n/Engine.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 9175cdf56..6b369946f 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -552,6 +552,49 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM ഷേഡർ റീലോഡ് പരാജയപ്പെട്ടു, rgba/rgbx ലേക്ക് മാറുന്നു."); huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "മോണിറ്റർ {name}: വൈഡ് കളർ ഗാമട്ട് പ്രവർത്തനക്ഷമമാണെങ്കിലും, മോഡ് 10-bit അല്ല."); + // nl_NL (Dutch) + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_TITLE, "Applicatie Reageert Niet"); + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_CONTENT, "Een applicatie {title} - {class} reageert niet.\nWat wilt u doen?"); + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_OPTION_TERMINATE, "Beëindigen"); + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_OPTION_WAIT, "Wachten"); + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_PROP_UNKNOWN, "(onbekend)"); + + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Een applicatie {app} vraagt om een onbekende machtiging."); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Een applicatie {app} probeert uw scherm op te nemen.\n\nWilt u dit toestaan?"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Een applicatie {app} probeert een plugin te laden: {plugin}.\n\nWilt u dit toestaan?"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, + "Een nieuw toetsenbord is gedetecteerd: {keyboard}.\n\nWilt u toestemming geven dat het wordt gebruikt?"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(onbekend)"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_TITLE, "Toestemmingsverzoek"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tip: U kunt hiervoor vaste regels instellen in het Hyprland-configuratiebestand."); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_ALLOW, "Toestaan"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Toestaan en onthouden"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_ALLOW_ONCE, "Één keer toestaan"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_DENY, "Weigeren"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Onbekende applicatie (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "nl_NL", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "De XDG_CURRENT_DESKTOP omgevingsvariabele lijkt extern beheerd te worden en de huidige waarde is {value}.\nDit kan problemen veroorzaken, tenzij dit opzettelijk is."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_NO_GUIUTILS, + "Hyprland-guiutils is niet op uw systeem geïnstalleerd. Dit is een runtime-afhankelijkheid voor sommige dialogen. Overweeg het te installeren."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland kon {count} essentieel bestand niet laden, geef de pakketbeheerder van uw distro de schuld voor slecht verpakkingswerk!"; + return "Hyprland kon {count} essentiële bestanden niet laden, geef de pakketbeheerder van uw distro de schuld voor slecht verpakkingswerk!"; + }); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Uw monitorindeling is onjuist ingesteld. Monitor {name} overlapt met één of meerdere andere monitoren in de indeling.\n" + "Zie de wiki (Monitors pagina) voor meer informatie. Dit zal problemen veroorzaken."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + "Monitor {name} is er niet in geslaagd om een van de aangevraagde modi toe te passen en gebruikt nu de modus {mode}."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Ongeldige schaal opgegeven voor monitor {name}: {scale}, de voorgestelde schaal {fixed_scale} wordt gebruikt."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Plugin {name} kon niet worden geladen: {error}"); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Het opnieuw laden van de CM-shader is mislukt. Er wordt teruggevallen op rgba/rgbx."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: breed kleurbereik is ingeschakeld maar het scherm staat niet in 10-bitmodus."); + // pl_PL (Polish) huEngine->registerEntry("pl_PL", TXT_KEY_ANR_TITLE, "Aplikacja Nie Odpowiada"); huEngine->registerEntry("pl_PL", TXT_KEY_ANR_CONTENT, "Aplikacja {title} - {class} nie odpowiada.\nCo chcesz z nią zrobić?"); From 64e4e913e1bf3742605557bcbc14d3753a0075f2 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Mon, 17 Nov 2025 16:56:33 +0000 Subject: [PATCH 43/56] ci: fix translator ci --- .github/workflows/translation-ai-check.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index ba8243717..96c937512 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -1,9 +1,6 @@ name: AI Translation Check on: - pull_request_target: - types: - - opened issue_comment: types: - created @@ -14,24 +11,8 @@ permissions: issues: write jobs: - changes: - name: Check i18n changes - if: ${{ github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }} - runs-on: ubuntu-latest - steps: - - name: Checkout source code - uses: actions/checkout@v5 - - - uses: dorny/paths-filter@v3 - id: changes - with: - filters: | - i18n: - - 'src/i18n/**' review: name: Review Translation - needs: changes - if: ${{ needs.changes.outputs.backend == 'true' }} runs-on: ubuntu-latest env: From ff6d771be04a9e2c5093efd0e08d0e8108b1a82e Mon Sep 17 00:00:00 2001 From: Ali Ebadi Date: Mon, 17 Nov 2025 21:17:35 +0330 Subject: [PATCH 44/56] i18n: add Persian translations (#12361) --- src/i18n/Engine.cpp | 70 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 6b369946f..e7e80eaa1 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -300,6 +300,76 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("es", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Error al recargar el sombreador CM, recurriendo a rgba/rgbx."); huEngine->registerEntry("es", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: la gama de colores amplia está habilitada, pero la pantalla no está en modo de 10-bit."); + // fa_IR (Persian) + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_TITLE, "برنامه پاسخ نمی‌دهد"); + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_CONTENT, "برنامه {title} - {class} پاسخی نمی‌دهد.\nمی‌خواهید چه کاری انجام شود؟"); + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_OPTION_TERMINATE, "بستن برنامه"); + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_OPTION_WAIT, "صبر کنید"); + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_PROP_UNKNOWN, "(نامشخص)"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "برنامه {app} در حال درخواست یک مجوز ناشناخته است."); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, + "برنامه {app} می‌خواهد صفحه‌نمایش شما را ضبط کند.\n\nآیا اجازه می‌دهید؟"); + + huEngine->registerEntry( + "fa_IR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "برنامه {app} می‌خواهد پلاگین {plugin} را بارگذاری کند.\n\nآیا اجازه می‌دهید پلاگین بارگذاری " + "شود؟"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, + "یک کیبورد جدید شناسایی شد: {keyboard}.\n\nآیا اجازه استفاده از آن را صادر می‌کنید؟"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(نامشخص)"); + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_TITLE, "درخواست مجوز"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, + "نکته: می‌توانید قوانین دائمی مرتبط را در فایل تنظیمات هایپرلند تعریف کنید."); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_ALLOW, "اجازه"); + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "اجازه و ذخیره"); + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_ALLOW_ONCE, "اجازه یک‌بار"); + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_DENY, "عدم اجازه"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "برنامه ناشناخته (شناسه Wayland: {wayland_id})"); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "متغیر XDG_CURRENT_DESKTOP توسط محیطی خارجی تنظیم شده است و مقدار فعلی آن {value} است.\n" + "اگر این کار عمدی نباشد ممکن است باعث ایجاد مشکل شود."); + + huEngine->registerEntry( + "fa_IR", TXT_KEY_NOTIF_NO_GUIUTILS, + "بستهٔ hyprland-guiutils در سیستم نصب نیست. این بسته برای برخی از پنجره‌ها و دیالوگ‌ها لازم است. نصب " + "آن " + "پیشنهاد " + "می‌شود."); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "هایپرلند نتوانست یک فایل ضروری را بارگذاری کند؛ ممکن است بسته‌بندی توزیع مشکل داشته " + "باشد."; + return "هایپرلند نتوانست {count} فایل ضروری را بارگذاری کند؛ ممکن است بسته‌بندی توزیع مشکل داشته " + "باشد."; + }); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "چیدمان مانیتورها صحیح نیست. مانیتور {name} با یک یا چند مانیتور دیگر تداخل دارد.\n" + "برای اطلاعات بیشتر به صفحهٔ مانیتورها در ویکی مراجعه کنید. این موضوع حتماً باعث مشکل " + "می‌شود."); + + huEngine->registerEntry( + "fa_IR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + "مانیتور {name} نتوانست هیچ‌کدام از حالت‌های درخواستی را اعمال کند؛ بازگشت به حالت {mode}."); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "مقیاس واردشده برای مانیتور {name} نامعتبر است: {scale}. مقیاس پیشنهادی اعمال شد: {fixed_scale}"); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "بارگذاری پلاگین {name} با خطا روبه‌رو شد: {error}"); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "بارگذاری دوبارهٔ شیدر CM ناموفق بود؛ از حالت rgba/rgbx استفاده شد."); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "مانیتور {name}: گسترهٔ رنگ وسیع فعال است اما نمایشگر در حالت ۱۰ بیتی نیست."); + // fr_FR (French) huEngine->registerEntry("fr_FR", TXT_KEY_ANR_TITLE, "L'application ne répond plus"); huEngine->registerEntry("fr_FR", TXT_KEY_ANR_CONTENT, "L'application {title} - {class} ne répond plus.\nQue voulez-vous faire?"); From 95ee08b3403b90e2930d46425cada1d280f3524e Mon Sep 17 00:00:00 2001 From: Vaxry Date: Mon, 17 Nov 2025 17:53:34 +0000 Subject: [PATCH 45/56] ci: fix translation ci again --- .github/workflows/translation-ai-check.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index 96c937512..d6a62a600 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -1,6 +1,9 @@ name: AI Translation Check on: + # pull_request_target: + # types: + # - opened issue_comment: types: - created @@ -13,8 +16,8 @@ permissions: jobs: review: name: Review Translation + if: ${{ github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }} runs-on: ubuntu-latest - env: OPENAI_MODEL: gpt-5-mini SYSTEM_PROMPT: | @@ -24,6 +27,20 @@ jobs: AI_PROMPT: Translation patch below. steps: + - name: Checkout source code + uses: actions/checkout@v5 + + - uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + i18n: + - 'src/i18n/**' + + - name: Stop if i18n not changed + if: steps.changes.outputs.i18n != 'true' + run: echo "No i18n changes in this PR; skipping." && exit 0 + - name: Determine PR number id: pr run: | 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 46/56] 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 d7b52a791..665d7f07d 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 014f386ac..ee0c34a38 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 a1408dc38..1bccaa2a2 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 1d0b68dcc..72120eaca 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 e1ba237fa..2ea93a14c 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 f37b94c35..bb03afd28 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 95f7caaef..e8759d28c 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 22bedcde3..c345fe718 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 6cfa061c5..2cf42eef5 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 def35d088..9d3802816 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 047001c26..ac28bc5a6 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 867b5b0c6..45fd273b8 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 a7f26ba6d..0f24a8bfc 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 bf6401e51..30b0f1bd0 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 5e5034518..f819e2930 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 b4b6ed3de..8efbe9458 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 7f32be419..599ee8e71 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 b5de65030..82a697157 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 95bb65b8c..d4f7aa149 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 d6bfcadf2..000000000 --- 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 7b6c8a6d9..000000000 --- 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 6278078d9..aab0b15ac 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 b70739cd3..5676e4d21 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 93f38de03..000000000 --- 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 9d3de70e8..000000000 --- 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 8671c0036..b27128869 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 0a7e207c3..63492682a 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 ea113d3e6..000000000 --- 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 dc6564ca6..000000000 --- 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 5bf462e95..000000000 --- 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 ee35313a0..7e1dcd5b6 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 000000000..3232035d6 --- /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 000000000..b0ea118e7 --- /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 000000000..fe7271a67 --- /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 000000000..2b852b3a5 --- /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 000000000..cb2157a6b --- /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 000000000..be7576729 --- /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 000000000..990796c12 --- /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 000000000..bb7da97fb --- /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 000000000..97f15b043 --- /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 000000000..17394239d --- /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 000000000..e3b3d26c4 --- /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 000000000..f5c472278 --- /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 000000000..bd162cdad --- /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 000000000..c5bc87f68 --- /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 000000000..2eda492c5 --- /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 000000000..0bc89d7fc --- /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 000000000..9588ac059 --- /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 000000000..14e30af19 --- /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 000000000..e980ce709 --- /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 000000000..d669822a8 --- /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 000000000..f8ef3e22a --- /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 000000000..abaa16577 --- /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 000000000..c70bc8b4d --- /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 000000000..75fd47394 --- /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 000000000..b0387b67b --- /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 000000000..944614ce6 --- /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 000000000..e6e0c6553 --- /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 000000000..ad80a0818 --- /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 000000000..660bf8716 --- /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 000000000..0827d462d --- /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 000000000..9ecfc890c --- /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 9e3b49d4d..dfbb9f2e8 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 4cf3c6716..d11e1be68 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 5feb2de97..183b6face 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 1a7b4ac64..ce9c69900 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 3c7071d54..7f377657e 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 f47320050..d18a0d29a 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 000000000..fb28628dc --- /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 000000000..1780e3eeb --- /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 4cdc17288..ca68751e6 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 6df544457..c653a47ea 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 702d6ac92..85b401bd0 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 5dde65d6d..5b2284c5f 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 8194f7394..feee03693 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 e3433a100..b4100beb0 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 f38f4ccf1..38efb829a 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 f156dfa97..8a3405544 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 82f43f47c..851e917a6 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 f44f21015..d1d8ec15c 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 59beb7121..ad2d0f451 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 e2a32c91b..84487a187 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 b33db9989..749390cd0 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 eb0a39aa6..c66c1f2b7 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 c38a10774..c64a8379c 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 2966ac900..98c8651f3 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 c0a3d30f4..b5b0041c5 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 66fcbca77..68bc7a417 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 548b2f8b8..b2ec69f3e 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 b115bab97..aec2bbc6e 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 75298ff75..a082f0738 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 bcc4c84e1..dd82abc53 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 dbf66b60b..3b95d7491 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(); } From 37fe7b2efde3f572080de33a1fef1b8306ee8bda Mon Sep 17 00:00:00 2001 From: Vaxry Date: Mon, 17 Nov 2025 18:43:04 +0000 Subject: [PATCH 47/56] config: export version variable for versioned configs fixes #12274 --- src/config/ConfigManager.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 8efbe9458..775d3beb0 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -990,12 +990,34 @@ std::string CConfigManager::getErrors() { return m_configErrors; } +static std::vector HL_VERSION_VARS = { + "HYPRLAND_V_0_53", +}; + +static void exportHlVersionVars() { + for (const auto& v : HL_VERSION_VARS) { + setenv(v, "1", 1); + } +} + +static void clearHlVersionVars() { + for (const auto& v : HL_VERSION_VARS) { + unsetenv(v); + } +} + void CConfigManager::reload() { EMIT_HOOK_EVENT("preConfigReload", nullptr); setDefaultAnimationVars(); resetHLConfig(); - m_configCurrentPath = getMainConfigPath(); - const auto ERR = m_config->parse(); + m_configCurrentPath = getMainConfigPath(); + + exportHlVersionVars(); + + const auto ERR = m_config->parse(); + + clearHlVersionVars(); + const auto monitorError = handleMonitorv2(); const auto ruleError = reloadRules(); m_lastConfigVerificationWasSuccessful = !ERR.error && !monitorError.error; From edc311544a54a06ce4acb759b4d9a30853695452 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Mon, 17 Nov 2025 22:37:53 +0000 Subject: [PATCH 48/56] dwindle: Revert rework split logic to be fully gap-aware (#12047) This reverts commit 151b5f69783b111831e8c5dc062904a221799d7b. Fixes #12380 --- hyprtester/src/tests/main/window.cpp | 44 +++------- hyprtester/src/tests/main/workspaces.cpp | 91 -------------------- src/layout/DwindleLayout.cpp | 105 ++++++----------------- src/layout/DwindleLayout.hpp | 13 --- 4 files changed, 39 insertions(+), 214 deletions(-) diff --git a/hyprtester/src/tests/main/window.cpp b/hyprtester/src/tests/main/window.cpp index 2cf42eef5..871875927 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -152,40 +152,22 @@ static bool test() { NLog::log("{}Testing window split ratios", Colors::YELLOW); { - const double INITIAL_RATIO = 1.25; - const int GAPSIN = 5; - const int GAPSOUT = 20; - const int BORDERSIZE = 2; - const int BORDERS = BORDERSIZE * 2; - const int MONITOR_W = 1920; - const int MONITOR_H = 1080; - - const float totalAvailableHeight = MONITOR_H - (GAPSOUT * 2); - const int HEIGHT = std::round(totalAvailableHeight) - BORDERS; - const float availableWidthForSplit = MONITOR_W - (GAPSOUT * 2) - GAPSIN; - - auto calculateFinalWidth = [&](double boxWidth, bool isLeftWindow) { - double gapLeft = isLeftWindow ? GAPSOUT : GAPSIN; - double gapRight = isLeftWindow ? GAPSIN : GAPSOUT; - return std::round(boxWidth - gapLeft - gapRight - BORDERS); - }; - - double geomBoxWidthA_R1 = (availableWidthForSplit * INITIAL_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0); - double geomBoxWidthB_R1 = MONITOR_W - geomBoxWidthA_R1; - const int WIDTH1 = calculateFinalWidth(geomBoxWidthB_R1, false); - - const double INVERTED_RATIO = 0.75; - double geomBoxWidthA_R2 = (availableWidthForSplit * INVERTED_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0); - double geomBoxWidthB_R2 = MONITOR_W - geomBoxWidthA_R2; - const int WIDTH2 = calculateFinalWidth(geomBoxWidthB_R2, false); - const int WIDTH_A_FINAL = calculateFinalWidth(geomBoxWidthA_R2, true); + const double RATIO = 1.25; + const double PERCENT = RATIO / 2.0 * 100.0; + const int GAPSIN = 5; + const int GAPSOUT = 20; + const int BORDERS = 2 * 2; + const int WTRIM = BORDERS + GAPSIN + GAPSOUT; + const int HEIGHT = 1080 - (BORDERS + (GAPSOUT * 2)); + const int WIDTH1 = std::round(1920.0 / 2.0 * (2 - RATIO)) - WTRIM; + const int WIDTH2 = std::round(1920.0 / 2.0 * RATIO) - WTRIM; OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25")); if (!spawnKitty("kitty_B")) return false; - NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH1, HEIGHT); + NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, 100 - PERCENT); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT)); OK(getFromSocket("/dispatch killwindow activewindow")); @@ -197,12 +179,12 @@ static bool test() { if (!spawnKitty("kitty_B")) return false; - NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH2, HEIGHT); + NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, PERCENT); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH2, HEIGHT)); OK(getFromSocket("/dispatch focuswindow class:kitty_A")); - NLog::log("{}Expecting kitty_A size: {},{}", Colors::YELLOW, WIDTH_A_FINAL, HEIGHT); - EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH_A_FINAL, HEIGHT)); + NLog::log("{}Expecting kitty_A to have the same width as the previous kitty_B", Colors::YELLOW); + EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT)); OK(getFromSocket("/keyword dwindle:default_split_ratio 1")); } diff --git a/hyprtester/src/tests/main/workspaces.cpp b/hyprtester/src/tests/main/workspaces.cpp index 9d3802816..622236dcf 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include "../shared.hpp" @@ -15,7 +14,6 @@ static int ret = 0; using namespace Hyprutils::OS; using namespace Hyprutils::Memory; -using namespace Hyprutils::Utils; #define UP CUniquePointer #define SP CSharedPointer @@ -361,95 +359,6 @@ static bool test() { NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); - NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW); - { - - CScopeGuard guard = {[&]() { - NLog::log("{}Cleaning up asymmetric gap test", Colors::YELLOW); - Tests::killAllWindows(); - OK(getFromSocket("/reload")); - }}; - - OK(getFromSocket("/dispatch workspace name:gap_split_test")); - OK(getFromSocket("r/keyword general:gaps_in 0")); - OK(getFromSocket("r/keyword general:border_size 0")); - OK(getFromSocket("r/keyword dwindle:split_width_multiplier 1.0")); - OK(getFromSocket("r/keyword workspace name:gap_split_test,gapsout:0 1000 0 0")); - - NLog::log("{}Testing default split (force_split = 0)", Colors::YELLOW); - OK(getFromSocket("r/keyword dwindle:force_split 0")); - - if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) { - return false; - } - - NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); - - Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); - - NLog::log("{}Testing force_split = 1", Colors::YELLOW); - OK(getFromSocket("r/keyword dwindle:force_split 1")); - - if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) { - return false; - } - - NLog::log("{}Expecting vertical split (B above A)", Colors::YELLOW); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); - - NLog::log("{}Expecting horizontal split (C left of B)", Colors::YELLOW); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); - - if (!Tests::spawnKitty("gaps_kitty_C")) { - return false; - } - - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0"); - - Tests::killAllWindows(); - EXPECT(Tests::windowCount(), 0); - - NLog::log("{}Testing force_split = 2", Colors::YELLOW); - OK(getFromSocket("r/keyword dwindle:force_split 2")); - - if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) { - return false; - } - - NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); - - NLog::log("{}Expecting horizontal split (C right of A)", Colors::YELLOW); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); - - if (!Tests::spawnKitty("gaps_kitty_C")) { - return false; - } - - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); - OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0"); - } - - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - NLog::log("{}Expecting 0 windows", Colors::YELLOW); EXPECT(Tests::windowCount(), 0); diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp index c653a47ea..4925a50e6 100644 --- a/src/layout/DwindleLayout.cpp +++ b/src/layout/DwindleLayout.cpp @@ -9,51 +9,14 @@ #include "../managers/EventManager.hpp" #include "xwayland/XWayland.hpp" -SWorkspaceGaps CHyprDwindleLayout::getWorkspaceGaps(const PHLWORKSPACE& pWorkspace) { - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(pWorkspace); - static auto PGAPSINDATA = CConfigValue("general:gaps_in"); - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* const PGAPSIN = sc((PGAPSINDATA.ptr())->getData()); - auto* const PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); - - SWorkspaceGaps gaps; - gaps.in = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); - gaps.out = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); - return gaps; -} - -SNodeDisplayEdgeFlags CHyprDwindleLayout::getNodeDisplayEdgeFlags(const CBox& box, const PHLMONITOR& monitor) { - return { - .top = STICKS(box.y, monitor->m_position.y + monitor->m_reservedTopLeft.y), - .bottom = STICKS(box.y + box.h, monitor->m_position.y + monitor->m_size.y - monitor->m_reservedBottomRight.y), - .left = STICKS(box.x, monitor->m_position.x + monitor->m_reservedTopLeft.x), - .right = STICKS(box.x + box.w, monitor->m_position.x + monitor->m_size.x - monitor->m_reservedBottomRight.x), - }; -} - void SDwindleNodeData::recalcSizePosRecursive(bool force, bool horizontalOverride, bool verticalOverride) { if (children[0]) { static auto PSMARTSPLIT = CConfigValue("dwindle:smart_split"); static auto PPRESERVESPLIT = CConfigValue("dwindle:preserve_split"); static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(workspaceID); - if (!PWORKSPACE) - return; - - const auto PMONITOR = PWORKSPACE->m_monitor.lock(); - if (!PMONITOR) - return; - - const auto edges = layout->getNodeDisplayEdgeFlags(box, PMONITOR); - auto [gapsIn, gapsOut] = layout->getWorkspaceGaps(PWORKSPACE); - - const Vector2D availableSize = box.size() - - Vector2D{(edges.left ? gapsOut.m_left : gapsIn.m_left / 2.f) + (edges.right ? gapsOut.m_right : gapsIn.m_right / 2.f), - (edges.top ? gapsOut.m_top : gapsIn.m_top / 2.f) + (edges.bottom ? gapsOut.m_bottom : gapsIn.m_bottom / 2.f)}; - if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0) - splitTop = availableSize.y * *PFLMULT > availableSize.x; + splitTop = box.h * *PFLMULT > box.w; if (verticalOverride) splitTop = true; @@ -64,28 +27,14 @@ void SDwindleNodeData::recalcSizePosRecursive(bool force, bool horizontalOverrid if (SPLITSIDE) { // split left/right - const float gapsAppliedToChild1 = (edges.left ? gapsOut.m_left : gapsIn.m_left / 2.f) + gapsIn.m_right / 2.f; - const float gapsAppliedToChild2 = gapsIn.m_left / 2.f + (edges.right ? gapsOut.m_right : gapsIn.m_right / 2.f); - const float totalGaps = gapsAppliedToChild1 + gapsAppliedToChild2; - const float totalAvailable = box.w - totalGaps; - - const float child1Available = totalAvailable * (splitRatio / 2.f); - const float FIRSTSIZE = child1Available + gapsAppliedToChild1; - - children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize(); - children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize(); + const float FIRSTSIZE = box.w / 2.0 * splitRatio; + children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize(); + children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize(); } else { // split top/bottom - const float gapsAppliedToChild1 = (edges.top ? gapsOut.m_top : gapsIn.m_top / 2.f) + gapsIn.m_bottom / 2.f; - const float gapsAppliedToChild2 = gapsIn.m_top / 2.f + (edges.bottom ? gapsOut.m_bottom : gapsIn.m_bottom / 2.f); - const float totalGaps = gapsAppliedToChild1 + gapsAppliedToChild2; - const float totalAvailable = box.h - totalGaps; - - const float child1Available = totalAvailable * (splitRatio / 2.f); - const float FIRSTSIZE = child1Available + gapsAppliedToChild1; - - children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize(); - children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize(); + const float FIRSTSIZE = box.h / 2.0 * splitRatio; + children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize(); + children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize(); } children[0]->recalcSizePosRecursive(force); @@ -167,7 +116,10 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for } // for gaps outer - const auto edges = getNodeDisplayEdgeFlags(pNode->box, PMONITOR); + const bool DISPLAYLEFT = STICKS(pNode->box.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x); + const bool DISPLAYRIGHT = STICKS(pNode->box.x + pNode->box.w, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); + const bool DISPLAYTOP = STICKS(pNode->box.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); + const bool DISPLAYBOTTOM = STICKS(pNode->box.y + pNode->box.h, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); const auto PWINDOW = pNode->pWindow.lock(); // get specific gaps and rules for this workspace, @@ -228,9 +180,9 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for } } - const auto GAPOFFSETTOPLEFT = Vector2D(sc(edges.left ? gapsOut.m_left : gapsIn.m_left), sc(edges.top ? gapsOut.m_top : gapsIn.m_top)); + const auto GAPOFFSETTOPLEFT = Vector2D(sc(DISPLAYLEFT ? gapsOut.m_left : gapsIn.m_left), sc(DISPLAYTOP ? gapsOut.m_top : gapsIn.m_top)); - const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc(edges.right ? gapsOut.m_right : gapsIn.m_right), sc(edges.bottom ? gapsOut.m_bottom : gapsIn.m_bottom)); + const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc(DISPLAYRIGHT ? gapsOut.m_right : gapsIn.m_right), sc(DISPLAYBOTTOM ? gapsOut.m_bottom : gapsIn.m_bottom)); calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2; calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding; @@ -398,6 +350,7 @@ void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dir } // get the node under our cursor + m_dwindleNodesData.emplace_back(); const auto NEWPARENT = &m_dwindleNodesData.back(); @@ -410,17 +363,8 @@ void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dir static auto PWIDTHMULTIPLIER = CConfigValue("dwindle:split_width_multiplier"); - const auto edges = getNodeDisplayEdgeFlags(NEWPARENT->box, PMONITOR); - - const auto WORKSPACE = g_pCompositor->getWorkspaceByID(PNODE->workspaceID); - auto [gapsIn, gapsOut] = getWorkspaceGaps(WORKSPACE); - // if cursor over first child, make it first, etc - const Vector2D availableSize = NEWPARENT->box.size() - - Vector2D{(edges.left ? gapsOut.m_left : gapsIn.m_left / 2.f) + (edges.right ? gapsOut.m_right : gapsIn.m_right / 2.f), - (edges.top ? gapsOut.m_top : gapsIn.m_top / 2.f) + (edges.bottom ? gapsOut.m_bottom : gapsIn.m_bottom / 2.f)}; - - const auto SIDEBYSIDE = availableSize.x > availableSize.y * *PWIDTHMULTIPLIER; + const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER; NEWPARENT->splitTop = !SIDEBYSIDE; static auto PFORCESPLIT = CConfigValue("dwindle:force_split"); @@ -675,8 +619,11 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn static auto PSMARTRESIZING = CConfigValue("dwindle:smart_resizing"); // get some data about our window - const auto PMONITOR = PWINDOW->m_monitor.lock(); - const auto edges = getNodeDisplayEdgeFlags(CBox{PWINDOW->m_position, PWINDOW->m_size}, PMONITOR); + const auto PMONITOR = PWINDOW->m_monitor.lock(); + const bool DISPLAYLEFT = STICKS(PWINDOW->m_position.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x); + const bool DISPLAYRIGHT = STICKS(PWINDOW->m_position.x + PWINDOW->m_size.x, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); + const bool DISPLAYTOP = STICKS(PWINDOW->m_position.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); + const bool DISPLAYBOTTOM = STICKS(PWINDOW->m_position.y + PWINDOW->m_size.y, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); if (PWINDOW->m_isPseudotiled) { if (!m_pseudoDragFlags.started) { @@ -724,10 +671,10 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn // construct allowed movement Vector2D allowedMovement = pixResize; - if (edges.left && edges.right) + if (DISPLAYLEFT && DISPLAYRIGHT) allowedMovement.x = 0; - if (edges.bottom && edges.top) + if (DISPLAYBOTTOM && DISPLAYTOP) allowedMovement.y = 0; if (*PSMARTRESIZING == 1) { @@ -737,10 +684,10 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn SDwindleNodeData* PHOUTER = nullptr; SDwindleNodeData* PHINNER = nullptr; - const auto LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || edges.right; - const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || edges.bottom; - const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || edges.left; - const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || edges.top; + const auto LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || DISPLAYRIGHT; + const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || DISPLAYBOTTOM; + const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || DISPLAYLEFT; + const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || DISPLAYTOP; const auto NONE = corner == CORNER_NONE; for (auto PCURRENT = PNODE; PCURRENT && PCURRENT->pParent; PCURRENT = PCURRENT->pParent) { diff --git a/src/layout/DwindleLayout.hpp b/src/layout/DwindleLayout.hpp index de80beedd..23f19956a 100644 --- a/src/layout/DwindleLayout.hpp +++ b/src/layout/DwindleLayout.hpp @@ -1,7 +1,6 @@ #pragma once #include "IHyprLayout.hpp" -#include "../config/ConfigDataValues.hpp" #include "../desktop/DesktopTypes.hpp" #include @@ -13,15 +12,6 @@ class CHyprDwindleLayout; enum eFullscreenMode : int8_t; -struct SNodeDisplayEdgeFlags { - bool top = false, bottom = false, left = false, right = false; -}; - -struct SWorkspaceGaps { - CCssGapData in; - CCssGapData out; -}; - struct SDwindleNodeData { SDwindleNodeData* pParent = nullptr; bool isNode = false; @@ -75,9 +65,6 @@ class CHyprDwindleLayout : public IHyprLayout { virtual void onDisable(); private: - SWorkspaceGaps getWorkspaceGaps(const PHLWORKSPACE& pWorkspace); - SNodeDisplayEdgeFlags getNodeDisplayEdgeFlags(const CBox& box, const PHLMONITOR& monitor); - std::list m_dwindleNodesData; struct { From 312073ce795f95cfd5587932b5ed2b13d59beac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Tom=C3=A1s=20Fern=C3=A1ndez=20Mart=C3=ADn?= Date: Tue, 18 Nov 2025 16:46:14 +0100 Subject: [PATCH 49/56] CMake: add min version for xkbcommon --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee0c34a38..f7634cb92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,7 +220,7 @@ pkg_check_modules( deps REQUIRED IMPORTED_TARGET - xkbcommon + xkbcommon>=1.11.0 uuid wayland-server>=1.22.90 wayland-protocols>=1.45 From e15409bbebf05e02f91031ce4f3430cc62eb00a5 Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Tue, 18 Nov 2025 17:50:05 +0200 Subject: [PATCH 50/56] CMake: fix GIT_COMMIT_MESSAGE parsing --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7634cb92..7062e7d98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,7 +183,7 @@ if(Git_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} branch --show-current WORKING_DIRECTORY ${GIT_TOPLEVEL} OUTPUT_VARIABLE GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%s --no-show-signature + execute_process(COMMAND sh "-c" "${GIT_EXECUTABLE} show -s --format=%s --no-show-signature | sed \"s/\\\"/\'/g\"" WORKING_DIRECTORY ${GIT_TOPLEVEL} OUTPUT_VARIABLE GIT_COMMIT_MESSAGE OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%cd --date=local --no-show-signature From 2c9c4d090537504e1cb4c02450c41a73f161a090 Mon Sep 17 00:00:00 2001 From: fazzi <18248986+fxzzi@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:32:33 +0000 Subject: [PATCH 51/56] windowrules: fix matching against xdgTag (#12393) --- src/desktop/rule/windowRule/WindowRule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp index b0387b67b..1bcbbc962 100644 --- a/src/desktop/rule/windowRule/WindowRule.cpp +++ b/src/desktop/rule/windowRule/WindowRule.cpp @@ -121,7 +121,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) { return false; break; case RULE_PROP_XDG_TAG: - if (w->xdgTag().has_value() && !engine->match(*w->xdgTag())) + if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag())) return false; break; case RULE_PROP_EXEC_TOKEN: From 9495f989b422158e42c2fae954e34729e4adacdb Mon Sep 17 00:00:00 2001 From: KAGEYAM4 <75798544+KAGEYAM4@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:03:02 +0530 Subject: [PATCH 52/56] =?UTF-8?q?hyprpm:=20remove=20-nn=20flag=20and=20mak?= =?UTF-8?q?e=20notification=20behaviour=20more=20consist=E2=80=A6=20(#1127?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [hyprpm] Remove -nn flag and make notification behaviour more consistent. Before -> -nn turns on -n explicitly, and many notify() are ran without checking the flag. After -> warning and error notification will always work, -n will only give visual confirmation that plugin loaded successfully, eye candy. * [hyprpm] Add -nn breaking change fallback to nofity users. Added deprecation warning for the --notify-fail flag. --- hyprpm/src/main.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/hyprpm/src/main.cpp b/hyprpm/src/main.cpp index 47a557e2c..777d1d46e 100644 --- a/hyprpm/src/main.cpp +++ b/hyprpm/src/main.cpp @@ -25,8 +25,8 @@ constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager ┃ ┣ Flags: ┃ -┣ --notify | -n → Send a hyprland notification for important events (including both successes and fail events). -┣ --notify-fail | -nn → Send a hyprland notification for fail events only. +┣ --notify | -n → Send a hyprland notification confirming successful plugin load. +┃ Warnings/Errors trigger notifications regardless of this flag. ┣ --help | -h → Show this menu. ┣ --verbose | -v → Enable too much logging. ┣ --force | -f → Force an operation ignoring checks (e.g. update -f). @@ -47,7 +47,7 @@ int main(int argc, char** argv, char** envp) { } std::vector command; - bool notify = false, notifyFail = false, verbose = false, force = false, noShallow = false; + bool notify = false, verbose = false, force = false, noShallow = false; std::string customHlUrl; for (int i = 1; i < argc; ++i) { @@ -58,7 +58,9 @@ int main(int argc, char** argv, char** envp) { } else if (ARGS[i] == "--notify" || ARGS[i] == "-n") { notify = true; } else if (ARGS[i] == "--notify-fail" || ARGS[i] == "-nn") { - notifyFail = notify = true; + // TODO: Deprecated since v.053.0. Remove in version>0.56.0 + std::println(stderr, "{}", failureString("Deprececated flag.")); + g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help."); } else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") { verbose = true; } else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") { @@ -149,8 +151,9 @@ int main(int argc, char** argv, char** envp) { if (ret2 != LOADSTATE_OK) return 1; - } else if (notify) + } else { g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers"); + } } else if (command[0] == "enable") { if (command.size() < 2) { std::println(stderr, "{}", failureString("Not enough args for enable.")); @@ -194,19 +197,17 @@ int main(int argc, char** argv, char** envp) { auto ret = g_pPluginManager->ensurePluginsLoadState(force); if (ret != LOADSTATE_OK) { - if (notify) { - switch (ret) { - case LOADSTATE_FAIL: - case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break; - case LOADSTATE_HEADERS_OUTDATED: - g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually."); - break; - default: break; - } + switch (ret) { + case LOADSTATE_FAIL: + case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break; + case LOADSTATE_HEADERS_OUTDATED: + g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually."); + break; + default: break; } return 1; - } else if (notify && !notifyFail) { + } else if (notify) { g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins"); } } else if (command[0] == "purge-cache") { From e4b40abce649dc64fd645cbc4226f47ac9d32bd9 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Tue, 18 Nov 2025 16:49:09 +0000 Subject: [PATCH 53/56] windowrules: bring back windowUpdateRules --- src/desktop/rule/windowRule/WindowRuleApplicator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp index e6e0c6553..14d7fbf15 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp @@ -5,6 +5,7 @@ #include "../../Window.hpp" #include "../../types/OverridableVar.hpp" #include "../../../managers/LayoutManager.hpp" +#include "../../../managers/HookSystemManager.hpp" #include @@ -639,4 +640,7 @@ void CWindowRuleApplicator::propertiesChanged(std::underlying_type_tforceRecalcFor(m_window.lock()); + + // for plugins + EMIT_HOOK_EVENT("windowUpdateRules", m_window.lock()); } From 6a8d3069926dc8b442b0e6bc5b8d4099de5a3131 Mon Sep 17 00:00:00 2001 From: Kamikadze <40305144+Kam1k4dze@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:54:54 +0500 Subject: [PATCH 54/56] examples: fix example config (#12394) --- example/hyprland.conf | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/example/hyprland.conf b/example/hyprland.conf index 1bccaa2a2..07b372c10 100644 --- a/example/hyprland.conf +++ b/example/hyprland.conf @@ -160,19 +160,19 @@ animations { # workspace = w[tv1], gapsout:0, gapsin:0 # workspace = f[1], gapsout:0, gapsin:0 # windowrule { -# name = smart-gaps-1 -# floating = false -# on_workspace = n[s:window] w[tv1] -# +# name = no-gaps-wtv1 +# match:float = false +# match:workspace = w[tv1] +# # border_size = 0 # rounding = 0 # } -# +# # windowrule { -# name = smart-gaps-2 -# floating = false -# on_workspace = n[s:window] f[1] -# +# name = no-gaps-f1 +# match:float = false +# match:workspace = f[1] +# # border_size = 0 # rounding = 0 # } @@ -319,7 +319,7 @@ windowrule { windowrule { # Fix some dragging issues with XWayland - match:name = fix-xwayland-drags + name = fix-xwayland-drags match:class = ^$ match:title = ^$ match:xwayland = true From 9f02dca8de5489689a7e31a2dfbf068c5dd3d282 Mon Sep 17 00:00:00 2001 From: xyrd <62749392+rxmlp@users.noreply.github.com> Date: Wed, 19 Nov 2025 02:00:13 +0100 Subject: [PATCH 55/56] =?UTF-8?q?i18n:=20add=20Norwegian=20Bokm=C3=A5l=20t?= =?UTF-8?q?ranslations=20(#12354)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n/Engine.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index e7e80eaa1..46d4c1aff 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -622,6 +622,46 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM ഷേഡർ റീലോഡ് പരാജയപ്പെട്ടു, rgba/rgbx ലേക്ക് മാറുന്നു."); huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "മോണിറ്റർ {name}: വൈഡ് കളർ ഗാമട്ട് പ്രവർത്തനക്ഷമമാണെങ്കിലും, മോഡ് 10-bit അല്ല."); + // nb_NO (Norwegian Bokmål) + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_TITLE, "Applikasjonen svarer ikke"); + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_CONTENT, "En applikasjon {title} - {class} svarer ikke.\nHva vil du gjøre med den?"); + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_OPTION_TERMINATE, "Avslutt"); + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_OPTION_WAIT, "Vent"); + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_PROP_UNKNOWN, "(ukjent)"); + + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "En applikasjon {app} ber om en ukjent tillatelse."); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "En applikasjon {app} prøver å fange skjermen din.\n\nVil du tillate den?"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "En applikasjon {app} prøver å laste en plugin: {plugin}.\n\nVil du tillate den?"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Et nytt tastatur er oppdaget: {keyboard}.\n\nVil du tillate at det opererer?"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(ukjent)"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_TITLE, "Tillatelsesforespørsel"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Hint: du kan angi vedvarende regler for disse i Hyprland konfigurasjonsfilen."); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_ALLOW, "Tillat"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Tillat og husk"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_ALLOW_ONCE, "Tillat en gang"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_DENY, "Nekte"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Ukjent applikasjon (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "nb_NO", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Ditt XDG_CURRENT_DESKTOP miljø ser ut til å være eksternt administrert, og den nåværende verdien er {value}.\nDette kan forårsake problemer med mindre det er bevisst."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_NO_GUIUTILS, + "Ditt system har ikke hyprland-guiutils installert. Dette er en kjøretidsavhengighet for noen dialoger. Vurder å installere den."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland kunne ikke laste {count} essensiell ressurs, skyld på distroens pakkeansvarlig for å ha gjort en dårlig jobb med pakkingen!"; + return "Hyprland kunne ikke laste {count} essensielle ressurser, skyld på distroens pakkeansvarlig for å ha gjort en dårlig jobb med pakkingen!"; + }); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Skjermoppsettet ditt er satt opp feil. Skjerm {name} overlapper med skjerm(er) i oppsettet.\nSjekk wiki (Skjerm oppsett siden) for " + "mer. Dette vil skape problemer."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Skjerm {name} feilet å sette de forespurte modusene, faller tilbake til modus {mode}."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Ugyldig skala sendt til skjerm {name}: {scale}, bruker foreslått skala: {fixed_scale}"); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Feilet å laste plugin {name}: {error}"); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader omlading feilet, faller tilbake til rgba/rgbx."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Skjerm {name}: bredt fargespekter er aktivert, men skjermen er ikke i 10-bit modus."); + // nl_NL (Dutch) huEngine->registerEntry("nl_NL", TXT_KEY_ANR_TITLE, "Applicatie Reageert Niet"); huEngine->registerEntry("nl_NL", TXT_KEY_ANR_CONTENT, "Een applicatie {title} - {class} reageert niet.\nWat wilt u doen?"); From fbb31503f1b69402eeda81ba75a547c862c88bf2 Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Mon, 17 Nov 2025 23:33:10 +0200 Subject: [PATCH 56/56] CI/AI translate: fix yet again --- .github/workflows/translation-ai-check.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml index d6a62a600..0729e9f61 100644 --- a/.github/workflows/translation-ai-check.yml +++ b/.github/workflows/translation-ai-check.yml @@ -14,9 +14,24 @@ permissions: issues: write jobs: + changes: + name: Check i18n changes + if: ${{ github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }} + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v5 + + - uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + i18n: + - 'src/i18n/**' review: name: Review Translation - if: ${{ github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }} + needs: changes + if: ${{ needs.changes.outputs.i18n == 'true' }} runs-on: ubuntu-latest env: OPENAI_MODEL: gpt-5-mini