aboutsummaryrefslogtreecommitdiff
path: root/working/hyprland-lua-port/reader-changes-for-lua.patch
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-08-24 14:27:57 -0600
committerCraig Jennings <c@cjennings.net>2026-08-24 14:27:57 -0600
commit8b5e438d787ff73c3344b4c5ea1141cf8b23ef69 (patch)
tree25f64dea1f918117648a6e0a383da208b2e2018e /working/hyprland-lua-port/reader-changes-for-lua.patch
parent1bb5955b121d650302ba3eab08e44cc004470a91 (diff)
downloadarchsetup-8b5e438d787ff73c3344b4c5ea1141cf8b23ef69.tar.gz
archsetup-8b5e438d787ff73c3344b4c5ea1141cf8b23ef69.zip
chore: stage the Hyprland Lua port with its redeploy kitHEADmain
Hyprland 0.57 drops the .conf format. The port is built and measured identical to the current config in a nested compositor — 38 settings, 103 binds, the host-override chain — but it is not deployed. I put it live this afternoon and took it back out the same hour, because it had not been run on real hardware and the machine has to stay usable until it has. working/hyprland-lua-port/ carries what the switch needs, including the two reader changes as replayable patches. Those matter more than the config itself: four things read hyprland.conf as a source of truth, and dotfiles-validate globs hypr/*.conf, so with the .conf gone it finds nothing and reports a clean tree. Two facts not worth re-deriving: with both files present 0.56.2 loads the .lua, so keeping the .conf beside it buys no rollback; and a running compositor rewrites a stub config within a second of the file vanishing, which is what fights a restow. Also archives finished tasks and folds in several sessions of task-review edits.
Diffstat (limited to 'working/hyprland-lua-port/reader-changes-for-lua.patch')
-rw-r--r--working/hyprland-lua-port/reader-changes-for-lua.patch241
1 files changed, 241 insertions, 0 deletions
diff --git a/working/hyprland-lua-port/reader-changes-for-lua.patch b/working/hyprland-lua-port/reader-changes-for-lua.patch
new file mode 100644
index 0000000..747b047
--- /dev/null
+++ b/working/hyprland-lua-port/reader-changes-for-lua.patch
@@ -0,0 +1,241 @@
+diff --git a/common/.local/bin/dotfiles-validate b/common/.local/bin/dotfiles-validate
+index 57a7505..d5e6695 100755
+--- a/common/.local/bin/dotfiles-validate
++++ b/common/.local/bin/dotfiles-validate
+@@ -3,6 +3,7 @@
+ #
+ # Walks the tree and extracts the commands that configs promise to launch:
+ # - hypr conf files: exec-once = CMD / exec = CMD / bind* = ..., exec, CMD
++# - hypr lua configs: at_start/at_shutdown/at_reload("CMD"), exec_cmd("CMD")
+ # - waybar config: "exec(-if)", "on-click*", "on-scroll-*",
+ # "on-double-click" values
+ # - systemd user units: Exec*= lines (leading -/@ modifiers stripped)
+@@ -45,6 +46,30 @@ find "$root" -path '*/.config/hypr/*.conf' -type f 2>/dev/null | while read -r f
+ ' "$f"
+ done >> "$refs_file"
+
++# --- hypr lua configs: autostart collectors and exec_cmd dispatchers ---
++# The Lua config manager (Hyprland 0.55+) spells the same two things as function
++# calls rather than assignments, so the .conf walk above sees none of them. Only
++# the first word is taken, as everywhere else here. The two matches are written
++# out rather than folded into a helper: awk cannot take a regex literal as a
++# function parameter -- it collapses to a boolean match against $0, which
++# silently turns every line into a bogus reference.
++find "$root" -path '*/.config/hypr/*.lua' -type f 2>/dev/null | while read -r f; do
++ awk -v file="$f" '
++ match($0, /at_(start|shutdown|reload)\("/) {
++ rest = substr($0, RSTART + RLENGTH)
++ sub(/".*$/, "", rest)
++ n = split(rest, w, /[ \t]+/)
++ if (n > 0 && w[1] != "") print file ":" FNR ":" w[1]
++ }
++ match($0, /hl\.dsp\.exec_cmd\("/) {
++ rest = substr($0, RSTART + RLENGTH)
++ sub(/".*$/, "", rest)
++ n = split(rest, w, /[ \t]+/)
++ if (n > 0 && w[1] != "") print file ":" FNR ":" w[1]
++ }
++ ' "$f"
++done >> "$refs_file"
++
+ # --- waybar configs: command-bearing JSON values ---
+ find "$root" -path '*/.config/waybar/*' -type f \( -name config -o -name '*.json' -o -name '*.jsonc' \) 2>/dev/null | while read -r f; do
+ awk -v file="$f" '
+diff --git a/tests/layout-cycle/test_layout_cycle.py b/tests/layout-cycle/test_layout_cycle.py
+index 1454817..3740024 100644
+--- a/tests/layout-cycle/test_layout_cycle.py
++++ b/tests/layout-cycle/test_layout_cycle.py
+@@ -18,6 +18,7 @@ Run from repo root:
+
+ import json
+ import os
++import re
+ import subprocess
+ import tempfile
+ import unittest
+@@ -25,7 +26,7 @@ import unittest
+ REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+ SCRIPT = os.path.join(REPO_ROOT, "hyprland/.local/bin/layout-cycle")
+ FAKE_HYPRCTL = os.path.join(os.path.dirname(__file__), "fake-hyprctl")
+-HYPRLAND_CONF = os.path.join(REPO_ROOT, "hyprland/.config/hypr/hyprland.conf")
++HYPRLAND_CFG = os.path.join(REPO_ROOT, "hyprland/.config/hypr/hyprland.lua")
+
+ FLASH = "rgba(ffd24aff)"
+ ACTIVE = "rgba(daa520ff)"
+@@ -326,7 +327,7 @@ class TestFlashAllBorders(LayoutCycleHarness):
+
+
+ class TestScrollLayoutDisabledInConfig(unittest.TestCase):
+- """Pin the hyprland.conf half of the scrolling disable.
++ """Pin the hyprland.lua half of the scrolling disable.
+
+ The script tests above prove the ring skips scrolling; these prove no
+ keybinding reaches it either, and that the freed Super+Shift+S chord
+@@ -335,10 +336,20 @@ class TestScrollLayoutDisabledInConfig(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+- with open(HYPRLAND_CONF) as f:
++ with open(HYPRLAND_CFG) as f:
+ cls.conf = f.read()
++ # Every bind in the Lua config is a top-level hl.bind() call; the
++ # locked/mouse/repeat variants that hyprlang spelled bindl/bindm/binde
++ # are the same call with an options table, so one prefix covers them.
+ cls.binds = [l for l in cls.conf.splitlines()
+- if l.strip().startswith(("bind", "bindl", "bindm"))]
++ if l.strip().startswith("hl.bind")]
++
++ def test_the_binds_were_actually_found(self):
++ """Guard the guard: a renamed call would empty the list and make both
++ assertions below pass against nothing."""
++ self.assertGreater(len(self.binds), 50,
++ "found almost no hl.bind lines -- the two assertions "
++ "below would pass vacuously")
+
+ def test_no_bind_selects_scrolling_layout(self):
+ offenders = [l for l in self.binds if "general:layout scrolling" in l]
+@@ -346,7 +357,7 @@ class TestScrollLayoutDisabledInConfig(unittest.TestCase):
+
+ def test_super_shift_s_is_fullscreen_screenshot(self):
+ shift_s = [l for l in self.binds
+- if "$mod SHIFT, S," in l]
++ if re.search(r'\bmod\s*\.\.\s*"\s*\+\s*SHIFT\s*\+\s*S"', l)]
+ self.assertEqual(len(shift_s), 1, msg=f"binds found: {shift_s}")
+ self.assertIn("screenshot fullscreen", shift_s[0])
+
+diff --git a/tests/settings/test_session_restore.py b/tests/settings/test_session_restore.py
+index a989d52..b2a2721 100644
+--- a/tests/settings/test_session_restore.py
++++ b/tests/settings/test_session_restore.py
+@@ -585,12 +585,32 @@ class TestCompositorWiring(unittest.TestCase):
+ script and passing tests while never being placed in the bar.
+ """
+
+- CONF = os.path.join(REPO_ROOT, "hyprland/.config/hypr/hyprland.conf")
++ CONF = os.path.join(REPO_ROOT, "hyprland/.config/hypr/hyprland.lua")
+
+ def _lines(self):
++ """The autostart commands, in launch order.
++
++ The Lua config collects startup commands with at_start("CMD") and one
++ hl.on("hyprland.start") handler at the bottom replays the list in order,
++ so position in this file is still position at launch -- which is what
++ the ordering assertion below reads. Returning the command strings rather
++ than raw lines means every entry here is by construction an autostart
++ command, so the old startswith("exec-once") filter has nothing left to do.
++ """
++ out = []
+ with open(self.CONF) as f:
+- return [ln.strip() for ln in f
+- if ln.strip() and not ln.strip().startswith("#")]
++ for ln in f:
++ m = re.search(r'at_start\("(.*)"\)', ln.strip())
++ if m:
++ out.append(m.group(1))
++ return out
++
++ def test_the_autostart_commands_were_actually_found(self):
++ """Guard the guard: a renamed collector would empty the list and make
++ every assertion below pass against nothing."""
++ self.assertGreater(len(self._lines()), 10,
++ "found almost no at_start commands -- the assertions "
++ "below would pass vacuously")
+
+ @staticmethod
+ def _is_toggle_restore(line):
+@@ -607,22 +627,19 @@ class TestCompositorWiring(unittest.TestCase):
+
+ def test_restore_runs_at_session_start(self):
+ self.assertTrue(
+- any(ln.startswith("exec-once") and self._is_toggle_restore(ln)
+- for ln in self._lines()),
+- "hyprland.conf has no exec-once running `settings restore` — "
++ any(self._is_toggle_restore(ln) for ln in self._lines()),
++ "hyprland.lua has no at_start running `settings restore` — "
+ "remembered toggles would never be replayed")
+
+ def test_wallpaper_restore_runs_at_session_start(self):
+ self.assertTrue(
+- any(ln.startswith("exec-once") and "settings restore-wallpaper" in ln
+- for ln in self._lines()),
+- "hyprland.conf has no exec-once running `settings restore-wallpaper` "
++ any("settings restore-wallpaper" in ln for ln in self._lines()),
++ "hyprland.lua has no at_start running `settings restore-wallpaper` "
+ "— the stored wallpaper would never be put back")
+
+ def test_the_toggle_restore_runs_only_once(self):
+ """Twice means the whole re-assert budget is spent twice per login."""
+- lines = [ln for ln in self._lines()
+- if ln.startswith("exec-once") and self._is_toggle_restore(ln)]
++ lines = [ln for ln in self._lines() if self._is_toggle_restore(ln)]
+ self.assertEqual(len(lines), 1, lines)
+
+ def test_restore_is_ordered_after_the_backings_it_corrects(self):
+@@ -631,17 +648,15 @@ class TestCompositorWiring(unittest.TestCase):
+ # burn attempts on backings that aren't up yet.
+ lines = self._lines()
+ restore = next(i for i, ln in enumerate(lines)
+- if ln.startswith("exec-once")
+- and self._is_toggle_restore(ln))
++ if self._is_toggle_restore(ln))
+ for backing in ("hypridle", "dunst"):
+- launch = next(i for i, ln in enumerate(lines)
+- if ln.startswith("exec-once") and backing in ln)
++ launch = next(i for i, ln in enumerate(lines) if backing in ln)
+ self.assertLess(launch, restore,
+ f"`settings restore` is ordered before {backing}")
+
+
+ class DimEnv(TempEnv):
+- """Adds the hyprctl fake, whose dim default is 1 -- as hyprland.conf's is."""
++ """Adds the hyprctl fake, whose dim default is 1 -- as hyprland.lua's is."""
+
+ def setUp(self):
+ super().setUp()
+diff --git a/tests/waybar-reserve/test_reserve_pairing.py b/tests/waybar-reserve/test_reserve_pairing.py
+index 7421692..fbeb95d 100644
+--- a/tests/waybar-reserve/test_reserve_pairing.py
++++ b/tests/waybar-reserve/test_reserve_pairing.py
+@@ -26,7 +26,7 @@ import unittest
+
+ REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+ WAYBAR = os.path.join(REPO_ROOT, "hyprland/.config/waybar/config")
+-HYPR = os.path.join(REPO_ROOT, "hyprland/.config/hypr/hyprland.conf")
++HYPR = os.path.join(REPO_ROOT, "hyprland/.config/hypr/hyprland.lua")
+
+
+ def waybar_config():
+@@ -35,13 +35,19 @@ def waybar_config():
+
+
+ def reserve_exec_wired():
+- """True when hyprland.conf runs waybar-reserve via exec (not exec-once).
++ """True when the config re-runs waybar-reserve on every config reload.
+
+- Matches waybar-reserve invoked anywhere in an ``exec =`` line, so the
+- reload-race-safe retry-loop form (``exec = for i in 1 2 3; do sleep 0.2;
+- waybar-reserve; done``) counts the same as a bare ``exec = waybar-reserve``."""
++ Matches waybar-reserve invoked anywhere in an ``at_reload(...)`` line, so the
++ reload-race-safe retry-loop form (``at_reload("for i in 1 2 3; do sleep 0.2;
++ waybar-reserve; done")``) counts the same as a bare ``at_reload("waybar-reserve")``.
++
++ at_reload and not at_start, because hyprlang's ``exec`` ran at startup AND on
++ every reload, and the Lua port splits those two jobs. at_reload's handler is
++ ``hl.on("config.reloaded")``, which fires on the initial load as well, so it
++ alone covers both. Matching at_start instead would pass while reservations
++ died on the next reload -- which is exactly the defect this guards."""
+ with open(HYPR) as f:
+- return any(re.match(r"\s*exec\s*=.*\bwaybar-reserve\b", ln) for ln in f)
++ return any(re.match(r'\s*at_reload\(".*\bwaybar-reserve\b', ln) for ln in f)
+
+
+ def reserve_target():
+@@ -61,7 +67,7 @@ class ReservePairingHarness(unittest.TestCase):
+
+ def test_hyprland_wires_the_reserve_script(self):
+ self.assertTrue(reserve_exec_wired(),
+- "hyprland.conf lacks `exec = waybar-reserve`: reservations "
++ "hyprland.lua lacks `at_reload(... waybar-reserve ...)`: reservations "
+ "die on the next config reload")
+
+ def test_reserve_target_covers_the_bar(self):