From fa0a654f5fccb46a329212984a0e2b25ba42ff25 Mon Sep 17 00:00:00 2001 From: rope Date: Tue, 20 Oct 2026 10:25:17 +0100 Subject: [PATCH] hyprland: dwindle resize only moves the dragged edge Co-Authored-By: Claude Opus 5.5 --- patches/hyprland-dwindle-pin-far-sides.patch | 91 ++++++++++++++++++++ settings/hyprland.nix | 9 +- 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 patches/hyprland-dwindle-pin-far-sides.patch diff --git a/patches/hyprland-dwindle-pin-far-sides.patch b/patches/hyprland-dwindle-pin-far-sides.patch new file mode 100644 index 0000000..c10966e --- /dev/null +++ b/patches/hyprland-dwindle-pin-far-sides.patch @@ -0,0 +1,91 @@ +--- a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp ++++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp +@@ -297,6 +297,56 @@ + recalculate(); + } + ++// Moving a split only changes that node's ratio, so every split nested on ++// either side of it gets rescaled and edges that weren't dragged slide along. ++// Record the size of each region that doesn't touch the moved edge, so it can ++// be pinned afterwards and only the windows along the edge absorb the resize. ++struct SFarSide { ++ SP node; ++ bool farIsSecond = false; ++ float size = 0; ++}; ++using FarSides = std::vector; ++ ++static void collectFarSides(const SP& node, bool vertical, bool edgeAtStart, FarSides& out) { ++ if (!node || !node->children[0] || !node->children[1]) ++ return; ++ ++ const auto C0 = node->children[0].lock(); ++ const auto C1 = node->children[1].lock(); ++ ++ if (node->splitTop == vertical) { ++ // split along the resize axis: pin the far child, follow the near one ++ const auto FARCHILD = edgeAtStart ? C1 : C0; ++ out.push_back({node, edgeAtStart, (float)(vertical ? FARCHILD->box.h : FARCHILD->box.w)}); ++ collectFarSides(edgeAtStart ? C0 : C1, vertical, edgeAtStart, out); ++ } else { ++ // perpendicular split: both children touch the edge ++ collectFarSides(C0, vertical, edgeAtStart, out); ++ collectFarSides(C1, vertical, edgeAtStart, out); ++ } ++} ++ ++static FarSides collectFarSides(const SP& split, bool vertical) { ++ FarSides out; ++ collectFarSides(split->children[0].lock(), vertical, false, out); ++ collectFarSides(split->children[1].lock(), vertical, true, out); ++ return out; ++} ++ ++// parents come before their children in `far`, so each node's box is already ++// final by the time its own ratio is recomputed ++static void restoreFarSides(const FarSides& far, bool vertical, bool force) { ++ for (const auto& [node, farIsSecond, size] : far) { ++ const float TOTAL = vertical ? node->box.h : node->box.w; ++ if (TOTAL <= 0) ++ continue; ++ ++ node->splitRatio = std::clamp(farIsSecond ? 2.f - size / TOTAL * 2.f : size / TOTAL * 2.f, 0.1F, 1.9F); ++ node->recalcSizePosRecursive(force); ++ } ++} ++ + void CDwindleAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (!validMapped(target->window())) + return; +@@ -356,6 +406,8 @@ + } + + if (PHOUTER) { ++ const auto HFAR = collectFarSides(PHOUTER->pParent.lock(), false); ++ + PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9); + + if (PHINNER) { +@@ -368,9 +420,13 @@ + PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } else + PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); ++ ++ restoreFarSides(HFAR, false, *PANIMATE == 0); + } + + if (PVOUTER) { ++ const auto VFAR = collectFarSides(PVOUTER->pParent.lock(), true); ++ + PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9); + + if (PVINNER) { +@@ -383,6 +439,8 @@ + PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } else + PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); ++ ++ restoreFarSides(VFAR, true, *PANIMATE == 0); + } + } else { + // get the correct containers to apply splitratio to diff --git a/settings/hyprland.nix b/settings/hyprland.nix index 63593c7..aee836f 100644 --- a/settings/hyprland.nix +++ b/settings/hyprland.nix @@ -11,6 +11,11 @@ in programs.hyprland = { enable = true; xwayland.enable = true; + # Dragging a tiled edge only resizes the windows touching it; stock + # dwindle rescales the whole subtree beyond it, so untouched edges slid. + package = pkgs.hyprland.overrideAttrs (old: { + patches = (old.patches or [ ]) ++ [ ../patches/hyprland-dwindle-pin-far-sides.patch ]; + }); # Closing a window that's being shared ends the share (Discord stops the # stream) instead of streaming its last frame forever. portalPackage = pkgs.xdg-desktop-portal-hyprland.overrideAttrs (old: { @@ -72,7 +77,7 @@ in libcanberra-gtk3 ]; - home-manager.users.fred = { config, lib, pkgs, inputs, ... }: + home-manager.users.fred = { config, osConfig, lib, pkgs, inputs, ... }: let c = config.lib.stylix.colors; rgb = hex: "rgb(${hex})"; @@ -218,6 +223,8 @@ in wayland.windowManager.hyprland = { enable = true; configType = "lua"; + # same patched build as programs.hyprland, not a second stock copy + package = osConfig.programs.hyprland.package; systemd.variables = [ "--all" ]; settings = {