Config window: every setting in config.toml, Hyprland options from Hyprland

All tunable values (sizes, timings, text sizes, colours, polls, equalizer
presets and bands, hotspot, extra output) are settings declared once in
config.rs, read from ~/.config/amane/config.toml, applied live on save.
shell.conf's four values move over on first start.

settings.rs: a normal window (control centre gear, or ipc config) with a
page per part, a page per Hyprland category, search and reset per row.
hypr.rs: Hyprland's options from hyprctl descriptions/getoption, applied
with hl.config and kept in ~/.config/hypr/amane.lua.
This commit is contained in:
server2
2026-10-15 02:18:57 +02:00
parent dfe3867529
commit 4fbfad3ce3
20 changed files with 3172 additions and 781 deletions
+53 -55
View File
@@ -6,13 +6,14 @@ use std::{env, fs, thread};
use amane::{Animation, Blend, Color, Service};
use crate::parse;
use crate::{config, parse};
/*
* the shell's colours. they come from the wallpaper: skwd-wall runs matugen with
* matugen/amane.colors, which writes ~/.config/amane/theme. ~/.config/amane/shell.conf
* says whether the open media card takes the song cover's colours instead, and how long
* a change cross-fades. without a theme file the shell keeps its original pink on purple
* matugen/amane.colors, which writes ~/.config/amane/theme. config.toml (config.rs) says
* whether the open media card takes the song cover's colours instead, how long a change
* cross-fades, and the colours used until there is a theme file. Theme also stands for
* config.toml: every view reads it, so a saved setting redraws the whole shell
*/
#[derive(Clone, Copy, PartialEq)]
pub struct Colors {
@@ -28,18 +29,23 @@ pub struct Colors {
pub surface_low: Color,
}
const PINK_ON_PURPLE: Colors = Colors {
accent: Color::rgb(247, 168, 200),
on_accent: Color::rgb(40, 18, 32),
accent_soft: Color::rgb(150, 82, 122),
flame: Color::rgb(255, 228, 240),
text: Color::rgb(241, 236, 246),
dim: Color::rgba(205, 192, 218, 160),
outline: Color::rgba(255, 255, 255, 26),
surface: Color::rgb(24, 18, 30),
surface_high: Color::rgb(35, 26, 44),
surface_low: Color::rgb(13, 10, 18),
};
// the colours without a theme file, from config.toml
fn fallback() -> Colors {
use config::appearance::fallback as colors;
Colors {
accent: colors::accent(),
on_accent: colors::on_accent(),
accent_soft: colors::accent_soft(),
flame: colors::flame(),
text: colors::text(),
dim: colors::dim(),
outline: colors::outline(),
surface: colors::surface(),
surface_high: colors::surface_high(),
surface_low: colors::surface_low(),
}
}
impl Colors {
// the background gradient's three stops, light to dark
@@ -70,42 +76,33 @@ impl Blend for Colors {
pub struct Theme {
colors: Animation<Colors>,
// the open media card and the equalizer take the cover's colours as their background
pub cover_colors: bool,
// the iwd access point profile the control centre's hotspot starts (/var/lib/iwd/ap/<name>.ap); empty: none
pub hotspot: String,
// a second Sound slider in the control centre, for the output whose name contains this; empty: none
pub extra_output: String,
}
impl Service for Theme {
fn new() -> Self {
Self {
colors: Animation::new(PINK_ON_PURPLE),
cover_colors: false,
hotspot: String::new(),
extra_output: String::new(),
}
Self { colors: Animation::new(fallback()) }
}
/*
* 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
* config.toml on its own thread, the theme here; either change re-reads both
*/
fn listen() {
reload();
thread::spawn(|| follow("shell.conf"));
thread::spawn(|| follow("config.toml"));
follow("theme");
}
}
// what was read last, (theme, shell.conf); none until the first read
// redraws every view, for a setting changed in the config window
pub fn touch() {
let _ = Theme::write();
}
// what was read last, (theme, config.toml); none until the first read
static LAST: Mutex<Option<(String, String)>> = Mutex::new(None);
fn follow(name: &str) {
@@ -119,7 +116,7 @@ fn follow(name: &str) {
// 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 read = (read("theme"), read("config.toml"));
let mut last = LAST.lock().unwrap_or_else(PoisonError::into_inner);
@@ -128,8 +125,12 @@ fn reload() {
return;
}
if last.as_ref().is_none_or(|(_, config)| *config != read.1) {
config::apply_text(&read.1);
}
// the colours at startup show at once, later changes cross-fade
apply(&read.0, &read.1, last.is_none());
apply(&read.0, last.is_none());
*last = Some(read);
}
@@ -138,18 +139,21 @@ fn read(name: &str) -> String {
fs::read_to_string(config_file(name)).unwrap_or_default()
}
// ~/.config/amane/<name>, or under $XDG_CONFIG_HOME
pub fn config_file(name: &str) -> PathBuf {
let config = env::var_os("XDG_CONFIG_HOME")
// $XDG_CONFIG_HOME, or ~/.config
pub fn config_home() -> PathBuf {
env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".config")))
.unwrap_or_default();
config.join("amane").join(name)
.unwrap_or_default()
}
fn apply(theme: &str, config: &str, startup: bool) {
let mut colors = PINK_ON_PURPLE;
// ~/.config/amane/<name>, or under $XDG_CONFIG_HOME
pub fn config_file(name: &str) -> PathBuf {
config_home().join("amane").join(name)
}
fn apply(theme: &str, startup: bool) {
let mut colors = fallback();
// a missing or broken line keeps that colour's default
for (key, value) in parse::settings(theme) {
@@ -174,17 +178,10 @@ fn apply(theme: &str, config: &str, startup: bool) {
}
}
let settings = parse::settings(config);
let setting = |name: &str| settings.iter().find(|(key, _)| key == name).map(|(_, value)| value.as_str());
let fade = if startup { 0 } else { setting("transition_ms").and_then(|ms| ms.parse().ok()).unwrap_or(400) };
let fade = if startup { 0 } else { config::appearance::colors::transition_ms() };
let mut theme = Theme::write();
theme.cover_colors = setting("cover_colors") == Some("true");
theme.hotspot = setting("hotspot").unwrap_or("").to_string();
theme.extra_output = setting("extra_output").unwrap_or("").to_string();
// a change in the middle of a fade starts from the colours on screen now
let now = theme.colors.value();
@@ -194,19 +191,20 @@ fn apply(theme: &str, config: &str, startup: bool) {
// the colours of the frame being drawn, set by the view that draws it
thread_local! {
static NOW: Cell<Colors> = const { Cell::new(PINK_ON_PURPLE) };
static NOW: Cell<Colors> = Cell::new(fallback());
}
/*
* a view's first line: everything it draws this frame uses these colours (and
* reading Theme redraws it when they change). says whether the media card takes the cover's
* reading Theme redraws it when they or config.toml change). says whether the media
* card takes the cover's
*/
pub fn begin() -> bool {
let theme = Theme::read();
NOW.set(theme.colors.value());
theme.cover_colors
config::appearance::colors::cover_colors()
}
pub fn now() -> Colors {