Compare commits
5 Commits
93229efb6e
...
ccd9cf5002
| Author | SHA1 | Date | |
|---|---|---|---|
| ccd9cf5002 | |||
| 6f6fd13b94 | |||
| 6b2200d640 | |||
| dcc1fdcd83 | |||
| 803ab94d22 |
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -148,7 +148,6 @@ dependencies = [
|
|||||||
"chrono-tz 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono-tz 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"i3monkit 0.1.2 (git+https://github.com/38/i3monkit)",
|
"i3monkit 0.1.2 (git+https://github.com/38/i3monkit)",
|
||||||
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"spark 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -364,11 +363,6 @@ dependencies = [
|
|||||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spark"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "structopt"
|
name = "structopt"
|
||||||
version = "0.2.18"
|
version = "0.2.18"
|
||||||
@ -523,7 +517,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
|
"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
|
||||||
"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704"
|
"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704"
|
||||||
"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85"
|
"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85"
|
||||||
"checksum spark 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdfdbc4ce5316afaa9023d28ed77019bc0860e68e6e5857643a14071520347cb"
|
|
||||||
"checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7"
|
"checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7"
|
||||||
"checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107"
|
"checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107"
|
||||||
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||||
|
|||||||
@ -25,4 +25,3 @@ num_cpus = "1.0"
|
|||||||
structopt = { version = "0.2", default-features = false }
|
structopt = { version = "0.2", default-features = false }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
chrono-tz = "0.5"
|
chrono-tz = "0.5"
|
||||||
spark = "0.4.0"
|
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
use chrono::NaiveTime;
|
use chrono::NaiveTime;
|
||||||
|
|
||||||
use i3monkit::widgets::CpuWidget;
|
|
||||||
use i3monkit::{ColorRGB, Header, I3Protocol, WidgetCollection};
|
use i3monkit::{ColorRGB, Header, I3Protocol, WidgetCollection};
|
||||||
|
|
||||||
use num_cpus;
|
use num_cpus;
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
use i3xs::widgets::cpu::CpuWidget;
|
||||||
use i3xs::widgets::datetime::{DateTimeWidget, TimeColor};
|
use i3xs::widgets::datetime::{DateTimeWidget, TimeColor};
|
||||||
use i3xs::widgets::network::NetworkSpeedWidget;
|
use i3xs::widgets::network::NetworkSpeedWidget;
|
||||||
|
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
|
pub mod spark;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|||||||
51
src/spark.rs
Normal file
51
src/spark.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use std::f32::MAX as f32_max;
|
||||||
|
|
||||||
|
/// `graph` generates a string representation of the values as a sparkline.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `values` - The values to graph.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let sparkline = spark::graph(&[1.0, 5.0, 22.0, 13.0, 53.0]);
|
||||||
|
/// assert_eq!(sparkline, "▁▁▃▂█");
|
||||||
|
/// ```
|
||||||
|
pub struct Graph {
|
||||||
|
pub min: Option<f32>,
|
||||||
|
pub max: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Graph {
|
||||||
|
pub fn render(&self, values: &[f32]) -> String {
|
||||||
|
let ticks = "▁▂▃▄▅▆▇█";
|
||||||
|
|
||||||
|
/* XXX: This doesn't feel like idiomatic Rust */
|
||||||
|
let mut min: f32 = self.min.unwrap_or(f32_max);
|
||||||
|
let mut max: f32 = self.max.unwrap_or(0.);
|
||||||
|
|
||||||
|
for &i in values.iter() {
|
||||||
|
if i > max {
|
||||||
|
max = i;
|
||||||
|
}
|
||||||
|
if i < min {
|
||||||
|
min = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ratio = if max == min {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
(ticks.chars().count() - 1) as f32 / (max - min)
|
||||||
|
};
|
||||||
|
|
||||||
|
values
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|n| (n - min) * ratio)
|
||||||
|
.map(|n| n.floor() as usize)
|
||||||
|
.filter_map(|n| ticks.chars().nth(n))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
190
src/widgets/cpu.rs
Normal file
190
src/widgets/cpu.rs
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
use std::collections::vec_deque::VecDeque;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufRead, BufReader, Result};
|
||||||
|
|
||||||
|
use i3monkit::{Block, ColorRGB, Widget, WidgetUpdate};
|
||||||
|
|
||||||
|
use crate::spark;
|
||||||
|
|
||||||
|
/// The CPU usage widget
|
||||||
|
///
|
||||||
|
/// This widget draws a CPU usage pertentage bar on your i3 status bar.
|
||||||
|
pub struct CpuWidget {
|
||||||
|
id: u32,
|
||||||
|
user: u64,
|
||||||
|
nice: u64,
|
||||||
|
system: u64,
|
||||||
|
idel: u64,
|
||||||
|
width: u8,
|
||||||
|
user_color: String,
|
||||||
|
nice_color: String,
|
||||||
|
system_color: String,
|
||||||
|
history: VecDeque<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_lerp(c1: ColorRGB, c2: ColorRGB, alpha: f32) -> ColorRGB {
|
||||||
|
// TODO(wathiede): actual lerp.
|
||||||
|
ColorRGB(
|
||||||
|
(c1.0 as f32 * (1. - alpha) + c2.0 as f32 * alpha) as u8,
|
||||||
|
(c1.1 as f32 * (1. - alpha) + c2.1 as f32 * alpha) as u8,
|
||||||
|
(c1.2 as f32 * (1. - alpha) + c2.2 as f32 * alpha) as u8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_to_string(c: ColorRGB) -> String {
|
||||||
|
format!("#{:02x}{:02x}{:02x}", c.0, c.1, c.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CpuWidget {
|
||||||
|
fn read_status(id: u32) -> Result<(u64, u64, u64, u64)> {
|
||||||
|
let name = format!("cpu{}", id);
|
||||||
|
let file = File::open("/proc/stat")?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
for line in reader.lines() {
|
||||||
|
let line = line?;
|
||||||
|
let tokens: Vec<_> = line.trim().split(|c| c == ' ' || c == '\t').collect();
|
||||||
|
if tokens[0] == name {
|
||||||
|
let parsed = tokens[1..5]
|
||||||
|
.iter()
|
||||||
|
.map(|x| u64::from_str_radix(x, 10).unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
return Ok((parsed[0], parsed[1], parsed[2], parsed[3]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"No such CPU core",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new CPU usage monitor widget for specified core
|
||||||
|
///
|
||||||
|
/// **id** The core id
|
||||||
|
pub fn new(id: u32) -> Self {
|
||||||
|
let (user, nice, system, idel) = Self::read_status(id).unwrap();
|
||||||
|
let num_samples = 6;
|
||||||
|
let ret = Self {
|
||||||
|
id,
|
||||||
|
user,
|
||||||
|
nice,
|
||||||
|
system,
|
||||||
|
idel,
|
||||||
|
width: 20,
|
||||||
|
user_color: "#00ff00".to_string(),
|
||||||
|
nice_color: "#0000ff".to_string(),
|
||||||
|
system_color: "#ff0000".to_string(),
|
||||||
|
history: (0..num_samples).map(|_| 0.).collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_history(&mut self) -> Option<String> {
|
||||||
|
let (user, nice, system, idel) = Self::read_status(self.id).ok()?;
|
||||||
|
let used_diff = (user + nice + system) - (self.user + self.nice + self.system) + 1;
|
||||||
|
let total_diff =
|
||||||
|
(user + nice + system + idel) - (self.user + self.nice + self.system + self.idel) + 1;
|
||||||
|
|
||||||
|
self.nice = nice;
|
||||||
|
self.user = user;
|
||||||
|
self.idel = idel;
|
||||||
|
self.system = system;
|
||||||
|
|
||||||
|
let percent = used_diff as f32 / total_diff as f32;
|
||||||
|
self.history.push_back(percent);
|
||||||
|
self.history.pop_front();
|
||||||
|
if self.history.len() > 0 {
|
||||||
|
let g = spark::Graph {
|
||||||
|
min: Some(0.),
|
||||||
|
max: Some(1.),
|
||||||
|
};
|
||||||
|
let sg = g.render(&self.history.iter().cloned().collect::<Vec<f32>>());
|
||||||
|
let colored_sg = self
|
||||||
|
.history
|
||||||
|
.iter()
|
||||||
|
.zip(sg.chars())
|
||||||
|
.map(|(v, g)| {
|
||||||
|
let c = color_lerp(ColorRGB::green(), ColorRGB::red(), *v);
|
||||||
|
format!(
|
||||||
|
r##"<span foreground="{}">{}</span>"##,
|
||||||
|
color_to_string(c),
|
||||||
|
g
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Some(colored_sg)
|
||||||
|
} else {
|
||||||
|
Some("N/A".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_bar(&mut self) -> Option<String> {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
for _ in 0..self.width {
|
||||||
|
ret.push("<span foreground=\"grey\">|</span>".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (user, nice, system, idel) = Self::read_status(self.id).ok()?;
|
||||||
|
|
||||||
|
let total_diff =
|
||||||
|
(user + nice + system + idel) - (self.user + self.nice + self.system + self.idel);
|
||||||
|
|
||||||
|
if total_diff > 0 {
|
||||||
|
let diffs = [system - self.system, nice - self.nice, user - self.user];
|
||||||
|
let color = [&self.system_color, &self.nice_color, &self.user_color];
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
for (d, c) in diffs.iter().zip(color.iter()) {
|
||||||
|
for _ in 0..(d * (self.width as u64) / total_diff) {
|
||||||
|
ret[idx] = format!("<span foreground=\"{}\">|</span>", c);
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = String::new();
|
||||||
|
for s in ret {
|
||||||
|
result.push_str(&s);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.nice = nice;
|
||||||
|
self.user = user;
|
||||||
|
self.idel = idel;
|
||||||
|
self.system = system;
|
||||||
|
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for CpuWidget {
|
||||||
|
fn update(&mut self) -> Option<WidgetUpdate> {
|
||||||
|
if self.history.len() > 0 {
|
||||||
|
if let Some(history) = self.draw_history() {
|
||||||
|
let mut data = Block::new();
|
||||||
|
|
||||||
|
data.use_pango();
|
||||||
|
data.append_full_text(&history);
|
||||||
|
|
||||||
|
return Some(WidgetUpdate {
|
||||||
|
refresh_interval: std::time::Duration::new(1, 0),
|
||||||
|
data: Some(data),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(bar) = self.draw_bar() {
|
||||||
|
let mut data = Block::new();
|
||||||
|
|
||||||
|
data.use_pango();
|
||||||
|
data.append_full_text(&format!("{}[{}]", self.id + 1, bar));
|
||||||
|
|
||||||
|
return Some(WidgetUpdate {
|
||||||
|
refresh_interval: std::time::Duration::new(1, 0),
|
||||||
|
data: Some(data),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
|
pub mod cpu;
|
||||||
pub mod datetime;
|
pub mod datetime;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use std::io::{BufRead, BufReader, Error, ErrorKind, Result};
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use spark;
|
use crate::spark;
|
||||||
|
|
||||||
const NETWORK_PATH_PREFIX: &'static str = "/sys/class/net";
|
const NETWORK_PATH_PREFIX: &'static str = "/sys/class/net";
|
||||||
const NETWORK_STAT_SUFFIX: &'static str = "statistics/dummy";
|
const NETWORK_STAT_SUFFIX: &'static str = "statistics/dummy";
|
||||||
@ -139,20 +139,24 @@ impl Widget for NetworkSpeedWidget {
|
|||||||
if let Ok((rx, tx)) = self.get_human_readable_stat() {
|
if let Ok((rx, tx)) = self.get_human_readable_stat() {
|
||||||
let mut data = Block::new();
|
let mut data = Block::new();
|
||||||
let (rx_history, tx_history) = if self.rx_history.len() > 1 {
|
let (rx_history, tx_history) = if self.rx_history.len() > 1 {
|
||||||
|
let g = spark::Graph {
|
||||||
|
min: None,
|
||||||
|
max: None,
|
||||||
|
};
|
||||||
(
|
(
|
||||||
spark::graph(&self.rx_history.iter().cloned().collect::<Vec<f32>>()),
|
g.render(&self.rx_history.iter().cloned().collect::<Vec<f32>>()),
|
||||||
spark::graph(&self.tx_history.iter().cloned().collect::<Vec<f32>>()),
|
g.render(&self.tx_history.iter().cloned().collect::<Vec<f32>>()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
("".to_string(), "".to_string())
|
("".to_string(), "".to_string())
|
||||||
};
|
};
|
||||||
data.use_pango();
|
data.use_pango();
|
||||||
data.append_full_text(&format!(
|
data.append_full_text(&format!(
|
||||||
"Rx:<tt>{}</tt>{} Tx:<tt>{}</tt>{}",
|
"Rx:<tt>{} {}</tt> Tx:<tt>{} {}</tt>",
|
||||||
rx, rx_history, tx, tx_history
|
rx, rx_history, tx, tx_history
|
||||||
));
|
));
|
||||||
return Some(WidgetUpdate {
|
return Some(WidgetUpdate {
|
||||||
refresh_interval: std::time::Duration::new(10, 0),
|
refresh_interval: std::time::Duration::new(1, 0),
|
||||||
data: Some(data),
|
data: Some(data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user