bread-theme: generate Hyprland layer rules from the shell theme (Phase 4a)

Adds `bread-theme layerrules`, which writes the active shell theme's
[compositor] table to ~/.config/hypr/layerrules.json (atomic write). This
lets ~/.config/hypr/scripts/ui/rules.lua read theme-driven blur/transparency/
animation for the breadbar/breadbox layer-shell namespaces instead of having
them hardcoded, following THEME_SYSTEM_PLAN.md §9. rules.lua keeps its
previous hardcoded rules as a pcall-guarded fallback for when the JSON is
missing or malformed (lives outside this repo, so not part of this commit).

Also fixes a pre-existing test race: shell::tests and the new
layerrules::tests both mutate XDG_CONFIG_HOME in parallel `cargo test`
threads but previously used separate, unrelated locks (or none), so they
could observe each other's env var changes mid-test. Both now share
bread_theme::test_support::XDG_CONFIG_HOME_LOCK.
This commit is contained in:
Breadway 2026-08-24 18:57:35 +08:00
parent 92d3362a6b
commit ea9758a5d5
6 changed files with 241 additions and 14 deletions

View file

@ -11,6 +11,8 @@
//! bread-theme print # render to stdout (no write)
//! bread-theme generate-output <OUTPUT> --image <PATH> [--shared]
//! bread-theme generate-output <OUTPUT> --from-json <PATH> [--shared]
//! bread-theme layerrules # write the active theme's [compositor] table
//! # to ~/.config/hypr/layerrules.json (plan §9)
use std::process::ExitCode;
@ -31,7 +33,7 @@ fn print_help() {
eprintln!(
"bread-theme — shared stylesheet generator\n\n\
USAGE:\n\
\x20 bread-theme [generate|reload|path|print]\n\
\x20 bread-theme [generate|reload|path|print|layerrules]\n\
\x20 bread-theme generate-output <OUTPUT> --image <PATH> [--shared]\n\
\x20 bread-theme generate-output <OUTPUT> --from-json <WAL-OR-PALETTE.json> [--shared]\n\n\
generate render the pywal palette to the shared stylesheet (default)\n\
@ -41,8 +43,14 @@ fn print_help() {
generate-output write palettes/<OUTPUT>.json and themes/<OUTPUT>.css\n\
\x20 --image isolated `wal -i` (does not touch ~/.cache/wal)\n\
\x20 --from-json wal colors.json or a color1-6 object\n\
\x20 --shared also write the session-global theme.css",
bread_theme::shared_css_path().display()
\x20 --shared also write the session-global theme.css\n\
layerrules write the active shell theme's [compositor] table to\n\
\x20 {} \n\
\x20 scripts/ui/rules.lua reads it for per-namespace blur/\n\
\x20 animation, falling back to its hardcoded rules if this\n\
\x20 is missing or malformed",
bread_theme::shared_css_path().display(),
bread_theme::layerrules_path().display()
);
}
@ -170,6 +178,19 @@ fn finish_generate_output(output: &str, css: std::path::PathBuf, shared: bool) -
}
}
fn layerrules_cmd() -> ExitCode {
match bread_theme::write_layerrules_active() {
Ok(path) => {
eprintln!("bread-theme: wrote {}", path.display());
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("bread-theme: failed to write layer rules: {e}");
ExitCode::FAILURE
}
}
}
fn main() -> ExitCode {
let cmd = std::env::args().nth(1).unwrap_or_else(|| "generate".into());
match cmd.as_str() {
@ -188,13 +209,14 @@ fn main() -> ExitCode {
// palette and recolour live — shared widgets *and* each app's own rules.
"reload" => write_and_report("reloaded"),
"generate-output" => generate_output_cmd(),
"layerrules" => layerrules_cmd(),
"-h" | "--help" | "help" => {
print_help();
ExitCode::SUCCESS
}
other => {
eprintln!(
"bread-theme: unknown command '{other}' (try generate|reload|path|print|generate-output)"
"bread-theme: unknown command '{other}' (try generate|reload|path|print|generate-output|layerrules)"
);
ExitCode::FAILURE
}