diff --git a/Cargo.lock b/Cargo.lock index 0d7e384..d4b5f49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,7 +22,7 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "breadmon" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index c914e08..4f6e0f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breadmon" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = "TUI monitor manager for Hyprland" license = "MIT" diff --git a/README.md b/README.md index dc02fc7..a0d2396 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ A terminal UI monitor manager for Hyprland. Lets you position, configure, and mi ## Requirements -- Hyprland compositor (the `hyprctl` binary must be on `PATH`) +- **[BOS (Bread OS)](https://git.breadway.dev/breadway/bos)'s patched Hyprland build.** Applying changes (the `a` key / Global keys "Apply") runs `hyprctl eval` with a `hl.monitor({...})` Lua call — a BOS-specific extension that does not exist on vanilla/upstream Hyprland. On a non-BOS Hyprland install, `hyprctl eval` itself is not a recognized request, and breadmon will fail to apply with an explicit error explaining this instead of the raw hyprctl response. Everything else in the TUI (viewing/arranging/saving profiles) works regardless; only the live-apply step needs BOS. +- The `hyprctl` binary must be on `PATH` - Rust toolchain (to build from source) ## Build diff --git a/src/monitor.rs b/src/monitor.rs index ca047aa..37f3128 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -367,6 +367,15 @@ pub async fn apply_monitors(monitors: &[Monitor]) -> Result<()> { let stdout = String::from_utf8_lossy(&output.stdout).trim().to_owned(); if stdout != "ok" { + if is_missing_eval_extension(&stdout) { + return Err(anyhow::anyhow!( + "breadmon: `hyprctl eval` (and the `hl.monitor()` Lua extension it uses \ + to apply monitor changes) is not available on this compositor. \ + This feature requires BOS (Bread OS)'s patched Hyprland build — it \ + does not exist on vanilla/upstream Hyprland. See the breadmon README \ + for details. (raw hyprctl response: {stdout:?})" + )); + } return Err(anyhow::anyhow!("hyprctl: {}", stdout)); } } @@ -382,6 +391,37 @@ pub async fn apply_monitors(monitors: &[Monitor]) -> Result<()> { Ok(()) } +/// Vanilla/upstream Hyprland doesn't recognize the `eval` request at all +/// (BOS's Hyprland fork adds it, along with the `hl.monitor()` Lua global +/// used above), and responds with an "unknown request" style message rather +/// than an error about `hl`. Detect that case so we can point the user at +/// the actual cause instead of surfacing the raw hyprctl text. +fn is_missing_eval_extension(hyprctl_stdout: &str) -> bool { + let s = hyprctl_stdout.to_lowercase(); + s.contains("unknown request") || s.contains("unknown command") +} + +#[cfg(test)] +mod eval_extension_tests { + use super::is_missing_eval_extension; + + #[test] + fn detects_unknown_request() { + assert!(is_missing_eval_extension("unknown request")); + assert!(is_missing_eval_extension("Unknown Request")); + } + + #[test] + fn does_not_flag_lua_errors() { + // A real Lua/apply-time error from a BOS build should still surface + // as a normal hyprctl error, not the "requires BOS" message. + assert!(!is_missing_eval_extension( + "error: attempt to call a nil value" + )); + assert!(!is_missing_eval_extension("ok")); + } +} + #[cfg(test)] mod tests { use super::*;