1use std::fmt;
2use std::fs;
3
4use nix::unistd;
5
6use colorsys::Rgb;
7
8use crate::common::{parse_colour_param, pct_value_hsl};
9
10#[derive(Debug, PartialEq)]
11pub enum CPUAppletError {
12 CPUCount,
13 CPUInfo,
14}
15
16impl std::error::Error for CPUAppletError {}
17
18impl fmt::Display for CPUAppletError {
19 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20 write!(f, "CPUAppletError: {:?}", self)
21 }
22}
23
24type Result<T> = std::result::Result<T, CPUAppletError>;
25
26pub fn cpu_count() -> Result<u32> {
27 let count = match unistd::sysconf(unistd::SysconfVar::_NPROCESSORS_ONLN) {
28 Ok(Some(c)) => c as u32,
29 Ok(None) => return Err(CPUAppletError::CPUCount),
30 Err(_) => return Err(CPUAppletError::CPUCount),
31 };
32 Ok(count)
33}
34
35#[derive(Debug, PartialEq)]
36pub struct CPUInfo {
37 pub min_freq: u32,
38 pub max_freq: u32,
39 pub cur_freq: u32,
40}
41
42fn read_u32_from_file(path: &str) -> Option<u32> {
43 let vstr = match fs::read_to_string(path) {
44 Ok(v) => v,
45 Err(_) => return None,
46 };
47
48 let vstr = vstr.trim_end();
49
50 vstr.parse::<u32>().ok()
51}
52
53fn normalise_cur_freq(cpu: &CPUInfo) -> f32 {
55 let mut c = cpu.cur_freq;
56 if c < cpu.min_freq {
57 c = cpu.min_freq
58 }
59 if c > cpu.max_freq {
60 c = cpu.max_freq
61 }
62 let range = cpu.max_freq - cpu.min_freq;
63 let adj = c - cpu.min_freq;
64 adj as f32 / range as f32
65}
66
67fn cpu_info(cpu_index: u32) -> Result<CPUInfo> {
68 let min_freq_path = format!("/sys/bus/cpu/devices/cpu{cpu_index}/cpufreq/scaling_min_freq");
69 let max_freq_path = format!("/sys/bus/cpu/devices/cpu{cpu_index}/cpufreq/scaling_max_freq");
70 let cur_freq_path = format!("/sys/bus/cpu/devices/cpu{cpu_index}/cpufreq/scaling_cur_freq");
71
72 match (read_u32_from_file(&min_freq_path), read_u32_from_file(&max_freq_path), read_u32_from_file(&cur_freq_path)) {
73 (Some(min), Some(max), Some(cur)) => Ok(CPUInfo { min_freq: min, max_freq: max, cur_freq: cur }),
74 _ => Err(CPUAppletError::CPUInfo),
75 }
76}
77
78impl fmt::Display for CPUInfo {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 write!(f, "CPUInfo: {:?}", self)
81 }
82}
83
84pub fn applet(args: &[String]) -> Result<()> {
85 let mut colour_s: Option<f32> = None;
86 let mut colour_l: Option<f32> = None;
87
88 for arg in args {
89 if let Some(s) = parse_colour_param(arg, "s") {
90 if (0.0..=100.0).contains(&s) {
91 colour_s = Some(s);
92 } else {
93 eprintln!("Saturation {s} out of range [0, 100.0]");
94 }
95 };
96 if let Some(l) = parse_colour_param(arg, "l") {
97 if (0.0..=100.0).contains(&l) {
98 colour_l = Some(l);
99 } else {
100 eprintln!("Lightness {l} out of range [0, 100.0]");
101 }
102 }
103 }
104
105 eprintln!("Saturation: {:?} Lightness: {:?}", colour_s, colour_l);
106
107 let c = cpu_count()?;
108
109 for i in 0..c {
110 let info = cpu_info(i)?;
111 let norm = normalise_cur_freq(&info) * 100.0;
112
113 let c = pct_value_hsl(norm, colour_s, colour_l);
114 let rgb = Rgb::from(&c);
115
116 eprintln!("CPU {i} Info: {info} Norm: {norm:.0}% RGB: {}", rgb.to_hex_string());
117
118 print!("#[bg={}] ", rgb.to_hex_string());
119 }
120 println!("#[default]");
121
122 Ok(())
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_normalise_cur_freq() {
131 assert_eq!(0.0, normalise_cur_freq(&CPUInfo { min_freq: 100, max_freq: 200, cur_freq: 99 }));
132 assert_eq!(0.0, normalise_cur_freq(&CPUInfo { min_freq: 100, max_freq: 200, cur_freq: 100 }));
133 assert_eq!(0.5, normalise_cur_freq(&CPUInfo { min_freq: 100, max_freq: 200, cur_freq: 150 }));
134 assert_eq!(1.0, normalise_cur_freq(&CPUInfo { min_freq: 100, max_freq: 200, cur_freq: 201 }));
135 }
136}