From 036b326b04d98aad33614c98f53782aa1de8e875 Mon Sep 17 00:00:00 2001 From: server2 Date: Fri, 9 Oct 2026 04:44:02 +0200 Subject: [PATCH] theme: react to file changes instead of polling the Theme service waits on amane::watch_file (inotify on the folder, so matugen's rename-over saves count) for both ~/.config/amane/theme and shell.conf, settles 50 ms for multi-write saves, and skips writes that change nothing. measured on the desktop: rename-over, in-place and sed -i writes picked up in 55-68 ms (was up to 500 ms), identical rewrites ignored, no activity while idle (was 4 reads a second). deleting the theme file takes effect at the next start --- AGENTS.md | 2 +- README.md | 2 +- matugen/amane.colors | 2 +- shell.conf | 2 +- src/theme.rs | 51 +++++++++++++++++++++++++++++++++----------- 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 669bc3c..6a4a2fc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ Notes for agents continuing work on the Magic Island shell. Read README.md first | `spectrum.rs` `Spectrum` | Service, 200 ms check | While the shown player plays: `pw-record --target \| cava -p $XDG_RUNTIME_DIR/magic-island/cava.conf` → 24 levels. Stops when it pauses. | | `art.rs` `Art` | Service, 250 ms poll | Cover URL → local file (curl for https), copied to `palette-cover`, which `Palette` (opened in `main.rs`, 8 colours) watches. | | `parse.rs` | plain functions, no amane | pactl/cava/proc parsing, filter-chain config, `pw-cli` params, EQ store, middle ellipsis, `key = value` settings and `#rrggbb`. Unit-tested on its own. | -| `theme.rs` | `Theme` Service, 500 ms poll | The shell's 10 colours from `~/.config/amane/theme` (rendered by Skwd-wall from `matugen/amane.colors`) and `shell.conf` (`cover_colors`, `transition_ms`), faded together on one `Animation`. Every view starts with `theme::begin()` (reads Theme, so it redraws on a change; returns `cover_colors`) and draws with `theme::now()`. No theme file: the original pink on purple. | +| `theme.rs` | `Theme` Service, `amane::watch_file` (inotify on the folder, so rename-over saves count) | The shell's 10 colours from `~/.config/amane/theme` (rendered by Skwd-wall from `matugen/amane.colors`) and `shell.conf` (`cover_colors`, `transition_ms`), faded together on one `Animation`. Every view starts with `theme::begin()` (reads Theme, so it redraws on a change; returns `cover_colors`) and draws with `theme::now()`. No theme file: the original pink on purple. | | `workspaces.rs` | view + input-only `Hover` Service | Workspace pill per monitor (namespace `magic-workspaces`, top left): `Workspaces` filtered by `output() == monitor.name`, special workspaces skipped, click = `hyprctl dispatch 'hl.dsp.focus({ workspace = N })'` (Hyprland 0.56 with a Lua config rejects the `dispatch workspace N` that amane's `Workspaces::focus` sends). Draws with `theme::now()`, reuses the island's `backdrop()` and `HOVER_MS`. Redraws only on Hyprland events and hover. | The equalizer: one `setsid -f pipewire -c eq-.conf` process per player with `libpipewire-module-filter-chain`, 10 biquads (low shelf 31 Hz, peaking 63 Hz–8 kHz, high shelf 16 kHz). The player's streams are moved into sink `magic_island_eq.`; gains change live with `pw-cli set-param magic_island_eq. Props '{ params = [ "b1:Gain" 6.0 ... ] }'`. A player only gets one when its curve or output is first changed, or it has a saved non-flat curve. diff --git a/README.md b/README.md index 9f7da86..6b5c4ce 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A Dynamic-Island-style media widget for Amane (Rust Wayland shell library), runn ## Colours -The shell takes its colours from the wallpaper through matugen. Skwd-wall v2 runs matugen on every new wallpaper and renders `matugen/amane.colors` into `~/.config/amane/theme`; the shell reads that file twice a second and cross-fades to the new colours. Without the file it keeps its original pink on purple. +The shell takes its colours from the wallpaper through matugen. Skwd-wall v2 runs matugen on every new wallpaper and renders `matugen/amane.colors` into `~/.config/amane/theme`; the shell picks the file up the moment it changes (inotify through `amane::watch_file`) and cross-fades to the new colours. Without the file it keeps its original pink on purple; deleting it takes effect at the next start. Register the template in Skwd-wall as a matugen integration: diff --git a/matugen/amane.colors b/matugen/amane.colors index 19cb8ba..6a802d9 100644 --- a/matugen/amane.colors +++ b/matugen/amane.colors @@ -1,5 +1,5 @@ # matugen template for amane: skwd-wall renders it on every new wallpaper into ~/.config/amane/theme, -# which the shell reads twice a second. key=value, one colour per line; a missing line keeps the default +# which the shell picks up the moment it changes. key=value, one colour per line; a missing line keeps the default accent={{colors.primary.default.hex}} on_accent={{colors.on_primary.default.hex}} accent_soft={{colors.primary_container.default.hex}} diff --git a/shell.conf b/shell.conf index 1480ce4..60fb354 100644 --- a/shell.conf +++ b/shell.conf @@ -1,4 +1,4 @@ -# amane shell settings, read twice a second: changes apply without a restart. +# amane shell settings, picked up the moment this file is saved: no restart needed. # the colours themselves come from the wallpaper (matugen/amane.colors, written to ./theme) # true: the open media card and equalizer take the song cover's colours as their background diff --git a/src/theme.rs b/src/theme.rs index 8b538f0..7d5baea 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,5 +1,6 @@ use std::cell::Cell; use std::path::PathBuf; +use std::sync::{Mutex, PoisonError}; use std::time::Duration; use std::{env, fs, thread}; @@ -82,25 +83,49 @@ impl Service for Theme { } } - // two small files read twice a second: matugen writes a new file each time, which a file watch can lose + /* + * waits on the kernel instead of polling: amane::watch_file watches the folder, so + * matugen writing a new file and renaming it over the old one is seen too. + * shell.conf on its own thread, the theme here; either change re-reads both + */ fn listen() { - let mut last = None; + reload(); - loop { - let read = (read("theme"), read("shell.conf")); + thread::spawn(|| follow("shell.conf")); - if last.as_ref() != Some(&read) { - // the colours at startup show at once, later changes cross-fade - apply(&read.0, &read.1, last.is_none()); - - last = Some(read); - } - - thread::sleep(Duration::from_millis(500)); - } + follow("theme"); } } +// what was read last, (theme, shell.conf); none until the first read +static LAST: Mutex> = Mutex::new(None); + +fn follow(name: &str) { + for _ in amane::watch_file(&config_file(name).to_string_lossy()) { + // one save can arrive as several writes: let it finish, then read once + thread::sleep(Duration::from_millis(50)); + + reload(); + } +} + +// a deleted theme file sends no event: the defaults come back at the next start +fn reload() { + let read = (read("theme"), read("shell.conf")); + + let mut last = LAST.lock().unwrap_or_else(PoisonError::into_inner); + + // a write that changes nothing redraws nothing + if last.as_ref() == Some(&read) { + return; + } + + // the colours at startup show at once, later changes cross-fade + apply(&read.0, &read.1, last.is_none()); + + *last = Some(read); +} + fn read(name: &str) -> String { fs::read_to_string(config_file(name)).unwrap_or_default() }