Skip to content

Commit

Permalink
Support x:<name> syntax to fetch color from .Xresources
Browse files Browse the repository at this point in the history
  • Loading branch information
jkorinth authored and bim9262 committed Jan 5, 2025
1 parent ee64ca2 commit 369147f
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 5 deletions.
11 changes: 7 additions & 4 deletions cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ words:
- bugz
- busctl
- caldav
- ccache
- chrono
- clippy
- CLOEXEC
- cmap
- conn
- consts
- conv
Expand Down Expand Up @@ -182,15 +184,16 @@ words:
- wofi
- wttr
- xclip
- xcolors
- xesam
- xkbswitch
- XKCD
- xrandr
- xresources
- xtask
- zbus
- zvariant
- zwlr
- zswap
- zram
- zswap
- Zswapped
- ccache
- zvariant
- zwlr
6 changes: 5 additions & 1 deletion doc/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ cpu_boost_off = "OFF"

# Available theme overrides

All `bg` and `fg` overrides are html hex color codes like `#000000` or `#789ABC`. A fourth byte for alpha (like `#acbdef42`) works on some systems. `00` is transparent, `FF` is opaque.
All `bg` and `fg` overrides are either

* html hex color codes like `#000000` or `#789ABC`; a fourth byte for alpha (like `#acbdef42`) works on some systems. `00` is transparent, `FF` is opaque, or
* a reference to another override, e.g., `{ link = "idle-bg" }`, or
* a reference to a color name defined in `~/.Xresources`, e.g., `x:background` looks for a line like `*background: #aabbcc` in `~/.Xresources` (see also [.Xresources](https://wiki.debian.org/Xresources)).

The tints are added to every second block counting from the right. They will therefore always brighten the block and never darken it. The alpha channel, if it works, can also be alternated in the same way.

Expand Down
1 change: 1 addition & 0 deletions src/themes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod color;
pub mod separator;
pub mod xresources;

use std::fmt;
use std::ops::{Deref, DerefMut};
Expand Down
6 changes: 6 additions & 0 deletions src/themes/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,12 @@ impl FromStr for Color {
let v = components.next().or_error(err_msg)??;
let a = components.next().unwrap_or(Ok(100.))?;
Color::Hsva(Hsva::new(h, s / 100., v / 100., (a / 100. * 255.) as u8))
} else if color.starts_with("x:") {
let name = color.split_at(2).1;
super::xresources::get_color(name)?
.or_error(|| format!("color '{name}' not defined in ~/.Xresources"))?
.parse()
.or_error(|| format!("invalid color definition '{name}'"))?
} else {
let err_msg = || format!("'{color}' is not a valid RGBA color");
let rgb = color.get(1..7).or_error(err_msg)?;
Expand Down
95 changes: 95 additions & 0 deletions src/themes/xresources.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use log::debug;
use regex::Regex;

use std::collections::HashMap;
use std::sync::LazyLock;

use crate::errors::*;

#[cfg(not(test))]
use std::{env, path::PathBuf};

#[cfg(not(test))]
fn read_xresources() -> std::io::Result<String> {
use std::io::{Error, ErrorKind};
let home =
env::var("HOME").map_err(|_| Error::new(ErrorKind::Other, "HOME env var was not set"))?;
let xresources = PathBuf::from(home + "/.Xresources");
debug!(".Xresources @ {:?}", xresources);
std::fs::read_to_string(xresources)
}

#[cfg(test)]
use tests::read_xresources;

static COLOR_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^\s*\*(?<name>[^: ]+)\s*:\s*(?<color>#[a-f0-9]{6,8}).*$").unwrap()
});

static COLORS: LazyLock<Result<HashMap<String, String>, Error>> = LazyLock::new(|| {
let content = read_xresources().error("could not read .Xresources")?;
debug!(".Xresources content:\n{}", content);
Ok(HashMap::from_iter(content.lines().filter_map(|line| {
COLOR_REGEX
.captures(line)
.map(|caps| (caps["name"].to_string(), caps["color"].to_string()))
})))
});

pub fn get_color(name: &str) -> Result<Option<&String>, Error> {
COLORS
.as_ref()
.map(|cmap| cmap.get(name))
.map_err(Clone::clone)
}

#[cfg(test)]
mod tests {
use super::*;
use std::io::Result;

#[allow(clippy::unnecessary_wraps)]
pub(crate) fn read_xresources() -> Result<String> {
static XRESOURCES: &str = "\
! this is a comment\n\
\n\
*color4 : #feedda\n\
\n\
*background: #ee33aa99\n\
";
Ok(XRESOURCES.to_string())
}

#[test]
fn test_reading_colors() {
let colors = COLORS.as_ref().unwrap();
assert_eq!(colors.get("color4"), Some(&"#feedda".to_string()));
assert_eq!(colors.get("background"), Some(&"#ee33aa99".to_string()));
assert_eq!(2, colors.len());
}

#[test]
fn test_deserializing_xcolors() {
use super::super::color::*;
let mut parsed_color = "x:color4".parse::<Color>().unwrap();
assert_eq!(
parsed_color,
Color::Rgba(Rgba {
r: 254,
g: 237,
b: 218,
a: 255
})
);
parsed_color = "x:background".parse::<Color>().unwrap();
assert_eq!(
parsed_color,
Color::Rgba(Rgba {
r: 238,
g: 51,
b: 170,
a: 153,
})
);
}
}

0 comments on commit 369147f

Please sign in to comment.