diff --git a/src/blocks/cpu.rs b/src/blocks/cpu.rs index dae6af8..0e179f5 100644 --- a/src/blocks/cpu.rs +++ b/src/blocks/cpu.rs @@ -1,6 +1,13 @@ +use std::sync::{atomic::AtomicBool, Arc}; + use systemstat::Platform; -use crate::Block; +use crate::{ + blocks::{BlockInfo, Color}, + Block, +}; + +use super::{BlockEvent, BlockPartName, BlockUpdate}; pub struct CpuBlock { pub id: usize, @@ -15,20 +22,46 @@ impl Block for CpuBlock { "cpu" } - fn run( - &mut self, - _event_r: flume::Receiver, - update_s: flume::Sender<(usize, String)>, - ) { + fn run(&mut self, _event_r: flume::Receiver, update_s: flume::Sender) { + let on = Arc::new(AtomicBool::new(false)); + { + let on = on.clone(); + std::thread::spawn(move || loop { + let event = _event_r.recv().unwrap(); + match event.part_name { + BlockPartName::Named(name) => { + if name == "icon" { + let on_value = on.load(std::sync::atomic::Ordering::Relaxed); + on.store(!on_value, std::sync::atomic::Ordering::Relaxed); + } + } + _ => (), + } + }); + } + let sys = systemstat::System::new(); loop { let cpu = sys.cpu_load_aggregate().unwrap(); std::thread::sleep(std::time::Duration::from_millis(1000)); let cpu = cpu.done().unwrap(); let load_percentage = 100.0 * cpu.user; - update_s - .send((self.id, format!(" Cpu: {:.2}% ", load_percentage))) - .unwrap(); + let fg_color = if on.load(std::sync::atomic::Ordering::Relaxed) { + Color::Rgb(0, 255, 0) + } else { + Color::Rgb(0, 0, 255) + }; + let update = BlockUpdate::Multi(vec![ + BlockInfo::from_unnamed(String::from(" ")).build(), + BlockInfo::from_named(String::from("icon"), String::from("\u{f3fd}")) + .fg_color(fg_color) + .bg_color(Color::Rgb(255, 255, 0)) + .build(), + BlockInfo::from_unnamed(format!("{:>5.2}% ", load_percentage)) + .fg_color(Color::Rgb(0, 255, 0)) + .build(), + ]); + update_s.send(update).unwrap(); } } } diff --git a/src/blocks/memory.rs b/src/blocks/memory.rs index f4b7c36..dfb994a 100644 --- a/src/blocks/memory.rs +++ b/src/blocks/memory.rs @@ -1,6 +1,8 @@ use systemstat::Platform; -use crate::Block; +use crate::{blocks::BlockInfo, Block}; + +use super::{BlockEvent, BlockUpdate}; pub struct MemoryBlock { pub id: usize, @@ -15,20 +17,17 @@ impl Block for MemoryBlock { "memory" } - fn run( - &mut self, - _event_r: flume::Receiver, - update_s: flume::Sender<(usize, String)>, - ) { + fn run(&mut self, _event_r: flume::Receiver, update_s: flume::Sender) { let sys = systemstat::System::new(); loop { let mem = sys.memory().unwrap(); let bytes_used = systemstat::saturating_sub_bytes(mem.total, mem.free).as_u64(); let bytes_total = mem.total.as_u64(); let mem_percentage = 100.0 * bytes_used as f64 / bytes_total as f64; - update_s - .send((self.id, format!(" Mem: {:.2}% ", mem_percentage))) - .unwrap(); + let update = BlockUpdate::Single( + BlockInfo::from_main(format!(" \u{f2db}{:>5.2}% ", mem_percentage)).build(), + ); + update_s.send(update).unwrap(); std::thread::sleep(std::time::Duration::from_millis(1000)); } } diff --git a/src/blocks/mod.rs b/src/blocks/mod.rs index c5d3a14..2e95879 100644 --- a/src/blocks/mod.rs +++ b/src/blocks/mod.rs @@ -1,3 +1,188 @@ pub mod cpu; pub mod memory; pub mod time; + +use std::ops::Deref; + +use serde::Serialize; + +use crate::protocol::i3bar_event::{I3BarEventButton, I3BarEventModifier}; + +pub trait Block { + fn id(&self) -> usize; + + fn name(&self) -> &str; + + fn run(&mut self, event_r: flume::Receiver, update_s: flume::Sender); +} + +#[derive(Clone, Debug)] +pub enum BlockUpdate { + Single(BlockInfo), + Multi(Vec), +} + +#[derive(Clone, Debug, Default)] +pub struct BlockInfo { + pub part_name: BlockPartName, + pub full_text: String, + pub short_text: Option, + pub fg_color: Color, + pub bg_color: Color, + pub border: Option, + pub min_width: Option, + pub urgent: Option, +} + +impl BlockInfo { + pub fn from_main(full_text: String) -> BlockInfoBuilder { + BlockInfoBuilder::new(full_text, BlockPartName::Main) + } + + pub fn from_named(name: String, full_text: String) -> BlockInfoBuilder { + BlockInfoBuilder::new(full_text, BlockPartName::Named(name)) + } + + pub fn from_unnamed(full_text: String) -> BlockInfoBuilder { + BlockInfoBuilder::new(full_text, BlockPartName::Unnamed) + } +} + +pub struct BlockInfoBuilder { + inner: BlockInfo, +} + +impl BlockInfoBuilder { + pub fn new(full_text: String, part_name: BlockPartName) -> Self { + Self { + inner: BlockInfo { + full_text, + part_name, + ..Default::default() + }, + } + } + + pub fn short_text(mut self, short_text: Option) -> Self { + self.inner.short_text = short_text; + self + } + + pub fn fg_color(mut self, fg_color: Color) -> Self { + self.inner.fg_color = fg_color; + self + } + + pub fn bg_color(mut self, bg_color: Color) -> Self { + self.inner.bg_color = bg_color; + self + } + + pub fn border(mut self, border: Option) -> Self { + self.inner.border = border; + self + } + + pub fn min_width(mut self, min_width: Option) -> Self { + self.inner.min_width = min_width; + self + } + + pub fn urgent(mut self, urgent: Option) -> Self { + self.inner.urgent = urgent; + self + } + + pub fn build(self) -> BlockInfo { + self.inner + } +} + +impl Deref for BlockInfoBuilder { + type Target = BlockInfo; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[derive(Clone, Debug)] +pub enum BlockPartName { + Main, + Unnamed, + Named(String), +} + +impl From<&str> for BlockPartName { + fn from(part_name_raw: &str) -> Self { + match part_name_raw { + "__main" => Self::Main, + "__unnamed" => Self::Unnamed, + s => Self::Named(s.to_owned()), + } + } +} + +impl From for String { + fn from(block_part_name: BlockPartName) -> Self { + match block_part_name { + BlockPartName::Main => String::from("__main"), + BlockPartName::Unnamed => String::from("__unnamed"), + BlockPartName::Named(s) => s, + } + } +} + +impl Default for BlockPartName { + fn default() -> Self { + Self::Main + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct BlockBorder { + pub color: Color, + pub border_top: Option, + pub top: Option, + pub right: Option, + pub bottom: Option, + pub left: Option, +} + +#[derive(Clone, Copy, Debug)] +pub enum Color { + Default, + Rgb(u8, u8, u8), + Rgba(u8, u8, u8, u8), +} + +impl Color { + pub fn to_hex(&self) -> Option { + match *self { + Color::Default => None, + Color::Rgb(r, g, b) => Some(format!("#{:02x}{:02x}{:02x}", r, g, b)), + Color::Rgba(r, g, b, a) => Some(format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a)), + } + } +} + +impl Default for Color { + fn default() -> Self { + Self::Default + } +} + +#[derive(Clone, Debug)] +pub struct BlockEvent { + pub part_name: BlockPartName, + pub button: I3BarEventButton, + pub modifiers: Vec, +} + +pub trait BlockConfig: Block { + type Config: Serialize; + + fn new(id: usize, block_config: Self::Config, shared_config: String) -> std::io::Result + where + Self: Sized; +} diff --git a/src/blocks/time.rs b/src/blocks/time.rs index b5d9814..876de9b 100644 --- a/src/blocks/time.rs +++ b/src/blocks/time.rs @@ -2,6 +2,8 @@ use chrono::Timelike; use crate::Block; +use super::{BlockEvent, BlockInfo, BlockUpdate}; + pub struct TimeBlock { pub id: usize, } @@ -15,15 +17,12 @@ impl Block for TimeBlock { "time" } - fn run( - &mut self, - _event_r: flume::Receiver, - update_s: flume::Sender<(usize, String)>, - ) { + fn run(&mut self, _event_r: flume::Receiver, update_s: flume::Sender) { loop { let now = chrono::Local::now(); - let time_string = now.format(" %a %d/%m %R ").to_string(); - update_s.send((self.id, time_string)).unwrap(); + let time_string = now.format(" \u{f017}%a %d/%m %R ").to_string(); + let update = BlockUpdate::Single(BlockInfo::from_main(time_string).build()); + update_s.send(update).unwrap(); let second = now.second() as u64; std::thread::sleep(std::time::Duration::from_secs(60 - second)); diff --git a/src/main.rs b/src/main.rs index 4ef96c4..852d33f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,12 @@ mod util; use std::collections::HashMap; -use protocol::i3bar_event::I3BarEvent; - -use crate::protocol::{ - i3bar_block::{write_blocks, I3BarBlock}, - i3bar_event::read_event, +use crate::{ + blocks::{Block, BlockPartName, BlockUpdate}, + protocol::{ + i3bar_block::{write_blocks, I3BarBlock}, + i3bar_event::read_event, + }, }; fn main() { @@ -27,20 +28,39 @@ fn main() { // Start all the blocks for mut block in blocks { + let block_id = block.id(); let (event_s, event_r) = flume::unbounded(); - event_senders.insert(block.id(), event_s); + event_senders.insert(block_id, event_s); + let (block_update_s, block_update_r) = flume::unbounded(); let update_s = update_s.clone(); + std::thread::spawn(move || loop { + let update = block_update_r.recv().unwrap(); + update_s.send((block_id, update)).unwrap(); + }); std::thread::spawn(move || { - block.run(event_r, update_s); + block.run(event_r, block_update_s); }); } // Receive events and send them to the correct blocks std::thread::spawn(move || loop { let event = read_event().unwrap(); - let id = event.instance.parse().unwrap(); - if let Some(event_sender) = event_senders.get(&id) { - event_sender.send(event).unwrap(); + let (id, part_name) = event.instance.split_once(',').unwrap(); + let id = id.parse().unwrap(); + let part_name = part_name.into(); + match part_name { + BlockPartName::Unnamed => (), + _ => { + if let Some(event_sender) = event_senders.get(&id) { + event_sender + .send(blocks::BlockEvent { + part_name: part_name.into(), + button: event.button, + modifiers: event.modifiers, + }) + .unwrap(); + } + } } }); @@ -50,30 +70,108 @@ fn main() { loop { let (id, content) = update_r.recv().unwrap(); cache.insert(id, content); - let rendered_blocks = block_metas - .iter() - .rev() - .filter_map(|(id, name)| cache.get(id).map(|content| (id, name, content.clone()))) - .map(|(id, name, content)| I3BarBlock { - full_text: content, - name: name.clone(), - instance: id.to_string(), - color: Some(String::from("#ffffff")), - ..Default::default() - }) - .collect(); - write_blocks(rendered_blocks); - } -} + let mut rendered_blocks = Vec::new(); + for (id, name) in block_metas.iter().rev() { + let block = match cache.get(id) { + Some(block) => block, + None => continue, + }; -pub trait Block { - fn id(&self) -> usize; + match block { + BlockUpdate::Single(block) => { + match block.part_name { + BlockPartName::Main => (), + _ => panic!("Single part blocks cannot be named or unnamed"), + } - fn name(&self) -> &str; + let block_part_name: String = block.part_name.to_owned().into(); + let instance_name = format!("{},{}", id, block_part_name); + let rendered_block = I3BarBlock { + name: name.to_owned(), + instance: instance_name, + full_text: block.full_text.to_owned(), + short_text: block.short_text.to_owned(), + color: block.fg_color.to_hex(), + background: block.bg_color.to_hex(), + border: block.border.and_then(|border| border.color.to_hex()), + border_top: block + .border + .and_then(|border| border.top) + .or_else(|| Some(0)), + border_right: block + .border + .and_then(|border| border.right) + .or_else(|| Some(0)), + border_bottom: block + .border + .and_then(|border| border.bottom) + .or_else(|| Some(0)), + border_left: block + .border + .and_then(|border| border.left) + .or_else(|| Some(0)), + min_width: block.min_width, + urgent: block.urgent, + ..Default::default() + }; + rendered_blocks.push(rendered_block); + } + BlockUpdate::Multi(blocks) => { + let blocks_len = blocks.len(); + for (i, block) in blocks.into_iter().enumerate() { + if let BlockPartName::Main = block.part_name { + panic!("Multi part blocks cannot have main") + } - fn run( - &mut self, - event_r: flume::Receiver, - update_s: flume::Sender<(usize, String)>, - ); + let block_part_name: String = block.part_name.to_owned().into(); + let instance_name = format!("{},{}", id, block_part_name); + let rendered_block = I3BarBlock { + name: name.to_owned(), + instance: instance_name, + full_text: block.full_text.to_owned(), + short_text: block.short_text.to_owned(), + color: block.fg_color.to_hex(), + background: block.bg_color.to_hex(), + border: block.border.and_then(|border| border.color.to_hex()), + border_top: block + .border + .and_then(|border| border.top) + .or_else(|| Some(0)), + border_right: block + .border + .and_then(|border| border.right) + .or_else(|| Some(0)), + border_bottom: block + .border + .and_then(|border| border.bottom) + .or_else(|| Some(0)), + border_left: block + .border + .and_then(|border| border.left) + .or_else(|| Some(0)), + min_width: block.min_width, + urgent: block.urgent, + separator: Some(i == blocks_len - 1), + separator_block_width: if i == blocks_len - 1 { None } else { Some(0) }, + ..Default::default() + }; + rendered_blocks.push(rendered_block); + } + } + } + } + // let rendered_blocks = block_metas + // .iter() + // .rev() + // .filter_map(|(id, name)| cache.get(id).map(|content| (id, name, content.clone()))) + // .map(|(id, name, content)| I3BarBlock { + // full_text: content, + // name: name.clone(), + // instance: id.to_string(), + // color: Some(String::from("#ffffff")), + // ..Default::default() + // }) + // .collect(); + write_blocks(rendered_blocks); + } } diff --git a/src/protocol/i3bar_event.rs b/src/protocol/i3bar_event.rs index d9bed9b..8249b53 100644 --- a/src/protocol/i3bar_event.rs +++ b/src/protocol/i3bar_event.rs @@ -25,7 +25,7 @@ pub fn read_event() -> std::io::Result { let slice = slice.trim_end_matches(|c| c != '}'); // If empty line, read another line - if slice == "" { + if slice.is_empty() { return read_event(); } diff --git a/src/util.rs b/src/util.rs index 496a092..bb5a73b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,6 @@ use std::io::Write; -pub fn _debug<'s, S1, S2>(loc: S1, content: S2) +pub fn _debug(loc: S1, content: S2) where S1: AsRef, S2: AsRef, @@ -8,7 +8,7 @@ where let mut file = std::fs::OpenOptions::new() .append(true) .create(true) - .open("/home/rosen/dev/i3status-simple/debug.log") + .open("/home/rosen/i3status-simple-debug.log") .unwrap(); file.write_all(format!("[{:?}]: {:?}\n", loc.as_ref(), content.as_ref()).as_bytes()) .unwrap();