diff --git a/README.md b/README.md index 98f6a9f..9abf59d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ The Material You scheme type is Skwd-wall's setting (`matugen.schemeType`). cover_colors = false # true: the open media card and equalizer take the song cover's colours as background transition_ms = 400 # how long a colour change cross-fades; 0 switches instantly glass = 1.0 # surface opacity, 0.05 to 1; below 1 the desktop shows through +blur = 3 # Hyprland's blur strength behind the glass, 1 to 30 (Hyprland default 8) ``` ## Glass @@ -48,6 +49,8 @@ amane_glass = hl.layer_rule({ The look of the surfaces (fill, rim, highlight, shadow) lives in `src/surface.rs`. +`blur` in `shell.conf` sets how much the glass frosts: low values leave the background nearly readable. Hyprland's blur strength is one global setting (a layer rule can only turn blur on), so the shell sets `decoration.blur.size` for everything Hyprland blurs, and sets it again after every Hyprland config reload. Leave `blur` out to keep Hyprland's own value. + ## Layout ``` diff --git a/shell.conf b/shell.conf index 7f105fe..e908b45 100644 --- a/shell.conf +++ b/shell.conf @@ -8,6 +8,11 @@ cover_colors = false # how long a colour change cross-fades, in milliseconds; 0 switches instantly transition_ms = 400 +# how strongly Hyprland blurs what is behind the glass: 1 (barely, nearly readable) to 30; +# Hyprland's own default is 8. its blur strength is global, so this also changes the blur +# of any other translucent window. leave it out to keep Hyprland's value +blur = 3 + # how opaque the shell's surfaces are, 0.05 to 1 (1 = solid). below 1 the desktop shows # through; to frost it, Hyprland has to blur behind the shell (README: Glass) glass = 0.4 diff --git a/src/theme.rs b/src/theme.rs index edf9528..dcf406c 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,5 +1,8 @@ use std::cell::Cell; +use std::io::{BufRead, BufReader}; +use std::os::unix::net::UnixStream; use std::path::PathBuf; +use std::process::Command; use std::sync::{Mutex, PoisonError}; use std::time::Duration; use std::{env, fs, thread}; @@ -97,6 +100,9 @@ impl Service for Theme { thread::spawn(|| follow("shell.conf")); + // a hyprland config reload puts its own blur back: ask for ours again + thread::spawn(follow_hyprland); + follow("theme"); } } @@ -130,6 +136,59 @@ fn reload() { *last = Some(read); } +/* + * shell.conf `blur`: how strongly Hyprland blurs behind the glass. hyprland's blur + * strength is global (a layer rule can only switch blur on), so this sets + * decoration.blur.size for everything it blurs. none leaves hyprland's own value + */ +static BLUR: Mutex> = Mutex::new(None); + +fn set_blur(size: Option) { + let changed = { + let mut blur = BLUR.lock().unwrap_or_else(PoisonError::into_inner); + + let changed = *blur != size; + + *blur = size; + + changed + }; + + if changed { + send_blur(); + } +} + +// on its own thread, so hyprctl never holds up the theme +fn send_blur() { + let Some(size) = *BLUR.lock().unwrap_or_else(PoisonError::into_inner) else { + return; + }; + + thread::spawn(move || { + let lua = format!("hl.config({{ [\"decoration.blur.size\"] = {size} }})"); + + let _ = Command::new("hyprctl").arg("eval").arg(lua).output(); + }); +} + +// hyprland's event socket says configreloaded>> after every reload, which resets the blur +fn follow_hyprland() { + let (Some(runtime), Some(instance)) = (env::var_os("XDG_RUNTIME_DIR"), env::var_os("HYPRLAND_INSTANCE_SIGNATURE")) else { + return; + }; + + let Ok(events) = UnixStream::connect(PathBuf::from(runtime).join("hypr").join(instance).join(".socket2.sock")) else { + return; + }; + + for line in BufReader::new(events).lines().map_while(Result::ok) { + if line.starts_with("configreloaded") { + send_blur(); + } + } +} + fn read(name: &str) -> String { fs::read_to_string(config_file(name)).unwrap_or_default() } @@ -180,6 +239,8 @@ fn apply(theme: &str, config: &str, startup: bool) { theme.cover_colors = setting("cover_colors") == Some("true"); theme.glass = setting("glass").and_then(|glass| glass.parse::().ok()).map_or(1.0, |glass| glass.clamp(0.05, 1.0)); + set_blur(setting("blur").and_then(|size| size.parse::().ok()).map(|size| size.clamp(1, 30))); + // a change in the middle of a fade starts from the colours on screen now let now = theme.colors.value();