diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2019-08-31 15:37:46 +0300 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2019-08-31 15:37:46 +0300 |
commit | 31bf144ecdfa3c1da9e38e1f6338329aab996680 (patch) | |
tree | b0aaaa045905bb78adc6e60af16b867e93c4e371 /src |
Initial commit
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 134 | ||||
-rw-r--r-- | src/ui.rs | 125 | ||||
-rw-r--r-- | src/ui/components.rs | 394 | ||||
-rw-r--r-- | src/ui/components/kernel.rs | 376 | ||||
-rw-r--r-- | src/ui/components/processes.rs | 334 | ||||
-rw-r--r-- | src/ui/components/utilities.rs | 282 | ||||
-rw-r--r-- | src/ui/components/utilities/widgets.rs | 69 | ||||
-rw-r--r-- | src/ui/state.rs | 356 | ||||
-rw-r--r-- | src/ui/terminal.rs | 29 | ||||
-rw-r--r-- | src/ui/terminal/cells.rs | 801 | ||||
-rw-r--r-- | src/ui/terminal/keys.rs | 239 | ||||
-rw-r--r-- | src/ui/terminal/position.rs | 180 | ||||
-rw-r--r-- | src/ui/text_processing.rs | 8 | ||||
-rw-r--r-- | src/ui/text_processing/grapheme_clusters.rs | 1845 | ||||
-rw-r--r-- | src/ui/text_processing/line_break.rs | 703 | ||||
-rw-r--r-- | src/ui/text_processing/tables.rs | 3389 | ||||
-rw-r--r-- | src/ui/text_processing/types.rs | 102 | ||||
-rw-r--r-- | src/ui/text_processing/wcwidth.rs | 661 | ||||
-rw-r--r-- | src/ui/types.rs | 61 |
19 files changed, 10088 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..68d51d6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,134 @@ +/* + * bb - bin.rs + * + * Copyright 2019 Manos Pitsidianakis + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see <http://www.gnu.org/licenses/>. + */ + +//! +//! This crate contains the frontend stuff of the application. The application entry way on +//! `src/bin.rs` creates an event loop and passes input to the `ui` module. +//! +//! The mail handling stuff is done in the `bb` crate which includes all backend needs. The +//! split is done to theoretically be able to create different frontends with the same innards. +//! + +extern crate crossbeam; +extern crate nix; +extern crate signal_hook; +extern crate termion; + +use crossbeam::channel::{bounded, tick}; +use crossbeam::select; +use libc::c_int; +use std::io::Error; +use std::time::{Duration, Instant}; + +mod ui; +use ui::*; + +fn notify(signals: &[c_int]) -> Result<crossbeam::channel::Receiver<c_int>, Error> { + let (s, r) = bounded(100); + let signals = signal_hook::iterator::Signals::new(signals)?; + std::thread::spawn(move || { + for signal in signals.forever() { + s.send(signal).unwrap(); + } + }); + Ok(r) +} + +fn main() -> Result<(), Error> { + let signals = &[ + signal_hook::SIGALRM, + signal_hook::SIGTERM, + signal_hook::SIGINT, + signal_hook::SIGQUIT, + /* Catch SIGWINCH to handle terminal resizing */ + signal_hook::SIGWINCH, + ]; + + let ticker = tick(Duration::from_millis(800)); + + let signal_recvr = notify(signals)?; + + /* Create the application State */ + let mut state = State::new(); + + let receiver = state.receiver(); + + let window = Box::new(HSplit::new( + Box::new(ui::components::KernelMetrics::new()), + Box::new(ui::components::ProcessList::new()), + 83, + false, + )); + + state.register_component(window); + + /* Keep track of the input mode. See ui::UIMode for details */ + 'main: loop { + 'inner: loop { + /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */ + select! { + recv(ticker) -> _ => { + state.redraw(true); + }, + recv(signal_recvr) -> sig => { + eprintln!("got signal {:?}", sig); + match sig.unwrap() { + signal_hook::SIGWINCH => { + state.update_size(); + state.render(); + state.redraw(true); + }, + _ => {} + } + }, + recv(receiver) -> msg => { + match msg.unwrap() { + ThreadEvent::Input(Key::Ctrl('z')) => { + state.switch_to_main_screen(); + //_thread_handler.join().expect("Couldn't join on the associated thread"); + let self_pid = nix::unistd::Pid::this(); + nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap(); + state.switch_to_alternate_screen(); + state.restore_input(); + // BUG: thread sends input event after one received key + state.update_size(); + state.render(); + state.redraw(true); + }, + ThreadEvent::Input(k) => { + match k { + Key::Char('q') | Key::Char('Q') => { + drop(state); + break 'main; + }, + key => { + state.rcv_event(UIEvent::Input(key)); + state.redraw(false); + }, + } + }, + ThreadEvent::UIEvent(_) => { + }, + } + }, + } + } // end of 'inner + } + Ok(()) +} diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..091d679 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,125 @@ +/* + * meli - ui crate. + * + * Copyright 2017-2018 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see <http://www.gnu.org/licenses/>. + */ + +/*! + * This library exports the public types and methods of its modules + */ + +mod text_processing; +pub use crate::ui::text_processing::*; +#[macro_use] +mod types; +pub use crate::ui::types::*; + +#[macro_use] +mod terminal; +pub use crate::ui::terminal::*; + +pub mod state; +pub use crate::ui::state::*; + +pub mod components; +pub use crate::ui::components::*; +pub use crate::ui::username::*; +pub mod username { + use libc; + use std::ptr::null_mut; + /* taken from whoami-0.1.1 */ + fn getpwuid() -> libc::passwd { + let mut pwentp = null_mut(); + let mut buffer = [0i8; 16384]; // from the man page + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd" + ))] + { + let mut pwent = libc::passwd { + pw_name: null_mut(), + pw_passwd: null_mut(), + pw_uid: 0, + pw_gid: 0, + pw_change: 0, + pw_class: null_mut(), + pw_gecos: null_mut(), + pw_dir: null_mut(), + pw_shell: null_mut(), + pw_expire: 0, + }; + unsafe { + libc::getpwuid_r( + libc::geteuid(), + &mut pwent, + &mut buffer[0], + 16384, + &mut pwentp, + ); + } + + pwent + } + #[cfg(target_os = "linux")] + { + let mut pwent = libc::passwd { + pw_name: null_mut(), + pw_passwd: null_mut(), + pw_uid: 0, + pw_gid: 0, + pw_gecos: null_mut(), + pw_dir: null_mut(), + pw_shell: null_mut(), + }; + + unsafe { + libc::getpwuid_r( + libc::geteuid(), + &mut pwent, + &mut buffer[0], + 16384, + &mut pwentp, + ); + } + + pwent + } + } + fn ptr_to_string(name: *mut i8) -> String { + let uname = name as *mut _ as *mut u8; + + let s; + let string; + + unsafe { + s = ::std::slice::from_raw_parts(uname, libc::strlen(name)); + string = String::from_utf8_lossy(s).to_string(); + } + + string + } + pub fn username() -> String { + let pwent = getpwuid(); + + ptr_to_string(pwent.pw_name) + } +} diff --git a/src/ui/components.rs b/src/ui/components.rs new file mode 100644 index 0000000..086da95 --- /dev/null +++ b/src/ui/components.rs @@ -0,0 +1,394 @@ +/* + * meli - ui crate. + * + * Copyright 2017-2018 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see <http://www.gnu.org/licenses/>. + */ + +/*! +Components are ways to handle application data. They can draw on the terminal and receive events, but also do other stuff as well. (For example, see the `notifications` module.) + +See the `Component` Trait for more details. +*/ + +use super::*; +use crate::ui::terminal::*; +use crate::ui::types::*; +mod utilities; +pub use utilities::*; + +mod kernel; +pub use kernel::*; +mod processes; +pub use processes::*; + +use std::collections::{HashMap, VecDeque}; +use std::fmt; +use std::fmt::{Debug, Display}; + +use super::{Key, StatusEvent, UIEvent}; +/// The upper and lower boundary char. +const HORZ_BOUNDARY: char = '─'; +/// The left and right boundary char. +const VERT_BOUNDARY: char = '│'; + +/// The top-left corner +const _TOP_LEFT_CORNER: char = '┌'; +/// The top-right corner +const _TOP_RIGHT_CORNER: char = '┐'; +/// The bottom-left corner +const _BOTTOM_LEFT_CORNER: char = '└'; +/// The bottom-right corner +const _BOTTOM_RIGHT_CORNER: char = '┘'; + +const LIGHT_VERTICAL_AND_RIGHT: char = '├'; + +const _LIGHT_VERTICAL_AND_LEFT: char = '┤'; + +const LIGHT_DOWN_AND_HORIZONTAL: char = '┬'; + +const LIGHT_UP_AND_HORIZONTAL: char = '┴'; + +const _DOUBLE_DOWN_AND_RIGHT: char = '╔'; +const _DOUBLE_DOWN_AND_LEFT: char = '╗'; +const _DOUBLE_UP_AND_LEFT: char = '╝'; +const _DOUBLE_UP_AND_RIGHT: char = '╚'; + +pub type ShortcutMap = HashMap<&'static str, Key>; +pub type ShortcutMaps = HashMap<String, ShortcutMap>; + +/// Types implementing this Trait can draw on the terminal and receive events. +/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its +/// fields (eg self.dirty = false) and act upon that in their `draw` implementation. +pub trait Component: Display + Debug + Send { + fn draw( + &mut self, + grid: &mut CellBuffer, + area: Area, + dirty_areas: &mut VecDeque<Area>, + tick: bool, + ); + fn process_event(&mut self, event: &mut UIEvent); + fn is_dirty(&self) -> bool { + true + } + fn set_dirty(&mut self); + fn get_shortcuts(&self) -> ShortcutMaps { + Default::default() + } +} + +/* +pub(crate) fn is_box_char(ch: char) -> bool { + match ch { + HORZ_BOUNDARY | VERT_BOUNDARY => true, + _ => false, + } +} + + * pub(crate) fn is_box_char(ch: char) -> bool { + * match ch { + * '└' | '─' | '┘' | '┴' | '┌' | '│' | '├' | '┐' | '┬' | '┤' | '┼' | '╷' | '╵' | '╴' | '╶' => true, + * _ => false, + * } + * } + */ + +fn bin_to_ch(b: u32) -> char { + match b { + 0b0001 => '╶', + 0b0010 => '╵', + 0b0011 => '└', + 0b0100 => '╴', + 0b0101 => '─', + 0b0110 => '┘', + 0b0111 => '┴', + 0b1000 => '╷', + 0b1001 => '┌', + 0b1010 => '│', + 0b1011 => '├', + 0b1100 => '┐', + 0b1101 => '┬', + 0b1110 => '┤', + 0b1111 => '┼', + x => unreachable!(format!("unreachable bin_to_ch(x), x = {:b}", x)), + } +} + +fn ch_to_bin(ch: char) -> Option<u32> { + match ch { + '└' => Some(0b0011), + '─' => Some(0b0101), + '┘' => Some(0b0110), + '┴' => Some(0b0111), + '┌' => Some(0b1001), + + '│' => Some(0b1010), + + '├' => Some(0b1011), + '┐' => Some(0b1100), + '┬' => Some(0b1101), + + '┤' => Some(0b1110), + + '┼' => Some(0b1111), + '╷' => Some(0b1000), + + '╵' => Some(0b0010), + '╴' => Some(0b0100), + '╶' => Some(0b0001), + _ => None, + } +} + +#[allow(clippy::never_loop)] +fn set_and_join_vert(grid: &mut CellBuffer, idx: Pos) -> u32 { + let (x, y) = idx; + let mut bin_set = 0b1010; + /* Check left side + * + * 1 + * -> 2 │ 0 + * 3 + */ + loop { + if x > 0 { + if let Some(cell) = grid.get_mut(x - 1, y) { + if let Some(adj) = ch_to_bin(cell.ch()) { + if (adj & 0b0001) > 0 { + bin_set |= 0b0100; + break; + } else if adj == 0b0100 { + cell.set_ch(bin_to_ch(0b0101)); + cell.set_fg(Color::Byte(240)); + bin_set |= 0b0100; + break; + } + } + } + } + bin_set &= 0b1011; + break; + } + + /* Check right side + * + * 1 + * 2 │ 0 <- + * 3 + */ + loop { + if let Some(cell) = grid.get_mut(x + 1, y) { + if let Some(adj) = ch_to_bin(cell.ch()) { + if (adj & 0b0100) > 0 { + bin_set |= 0b0001; + break; + } + } + } + bin_set &= 0b1110; + break; + } + + /* Set upper side + * + * 1 <- + * 2 │ 0 + * 3 + */ + loop { + if y > 0 { + if let Some(cell) = grid.get_mut(x, y - 1) { + if let Some(adj) = ch_to_bin(cell.ch()) { + cell.set_ch(bin_to_ch(adj | 0b1000)); + cell.set_fg(Color::Byte(240)); + } else { + bin_set &= 0b1101; + } + } + } + break; + } + + /* Set bottom side + * + * 1 + * 2 │ 0 + * 3 <- + */ + loop { + if let Some(cell) = grid.get_mut(x, y + 1) { + if let Some(adj) = ch_to_bin(cell.ch()) { + cell.set_ch(bin_to_ch(adj | 0b0010)); + cell.set_fg(Color::Byte(240)); + } else { + bin_set &= 0b0111; + } + } + break; + } + + if bin_set == 0 { + bin_set = 0b1010; + } + + bin_set +} + +#[allow(clippy::never_loop)] +fn set_and_join_horz(grid: &mut CellBuffer, idx: Pos) -> u32 { + let (x, y) = idx; + let mut bin_set = 0b0101; + /* Check upper side + * + * 1 <- + * 2 ─ 0 + * 3 + */ + loop { + if y > 0 { + if let Some(cell) = grid.get_mut(x, y - 1) { + if let Some(adj) = ch_to_bin(cell.ch()) { + if (adj & 0b1000) > 0 { + bin_set |= 0b0010; + break; + } else if adj == 0b0010 { + bin_set |= 0b0010; + cell.set_ch(bin_to_ch(0b1010)); + cell.set_fg(Color::Byte(240)); + break; + } + } + } + } + bin_set &= 0b1101; + break; + } + + /* Check bottom side + * + * 1 + * 2 ─ 0 + * 3 <- + */ + loop { + if let Some(cell) = grid.get_mut(x, y + 1) { + if let Some(adj) = ch_to_bin(cell.ch()) { + if (adj & 0b0010) > 0 { + bin_set |= 0b1000; + break; + } else if adj == 0b1000 { + bin_set |= 0b1000; + cell.set_ch(bin_to_ch(0b1010)); + cell.set_fg(Color::Byte(240)); + break; + } + } + } + bin_set &= 0b0111; + break; + } + + /* Set left side + * + * 1 + * -> 2 ─ 0 + * 3 + */ + loop { + if x > 0 { + if let Some(cell) = grid.get_mut(x - 1, y) { + if let Some(adj) = ch_to_bin(cell.ch()) { + cell.set_ch(bin_to_ch(adj | 0b0001)); + cell.set_fg(Color::Byte(240)); + } else { + bin_set &= 0b1011; + } + } + } + break; + } + + /* Set right side + * + * 1 + * 2 ─ 0 <- + * 3 + */ + loop { + if let Some(cell) = grid.get_mut(x + 1, y) { + if let Some(adj) = ch_to_bin(cell.ch()) { + cell.set_ch(bin_to_ch(adj | 0b0100)); + cell.set_fg(Color::Byte(240)); + } else { + bin_set &= 0b1110; + } + } + break; + } + + if bin_set == 0 { + bin_set = 0b0101; + } + + bin_set +} + +pub(crate) fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: char) { + /* Connected sides: + * + * 1 + * 2 c 0 + * 3 + * + * #3210 + * 0b____ + */ + + let bin_set = match ch { + '│' => set_and_join_vert(grid, idx), + '─' => set_and_join_horz(grid, idx), + _ => unreachable!(), + }; + + grid[idx].set_ch(bin_to_ch(bin_set)); + grid[idx].set_fg(Color::Byte(240)); +} + +pub fn create_box(grid: &mut CellBuffer, area: Area) { + if !is_valid_area!(area) { + return; + } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + + for x in get_x(upper_left)..get_x(bottom_right) { + grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY); + grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY); + grid[(x, get_y(bottom_right))].set_fg(Color::Byte(240)); + } + + for y in get_y(upper_left)..get_y(bottom_right) { + grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY); + grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY); + grid[(get_x(bottom_right), y)].set_fg(Color::Byte(240)); + } + set_and_join_box(grid, upper_left, HORZ_BOUNDARY); + set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY); + set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY); + set_and_join_box(grid, bottom_right, VERT_BOUNDARY); +} diff --git a/src/ui/components/kernel.rs b/src/ui/components/kernel.rs new file mode 100644 index 0000000..15d923b --- /dev/null +++ b/src/ui/components/kernel.rs @@ -0,0 +1,376 @@ +use super::*; +use std::fs::File; +use std::io::prelude::*; +use std::str::FromStr; + +/* Kernel metrics components */ +#[derive(Debug)] +pub struct KernelMetrics { + hostname: String, + kernel: String, + os_type: String, + uptime: String, + cpu_stat: Vec<Stat>, + boot_time: usize, + dirty: bool, +} + +impl fmt::Display for KernelMetrics { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "kernel") + } +} + +impl KernelMetrics { + pub fn new() -> Self { + let mut file = File::open("/proc/sys/kernel/hostname").unwrap(); + let mut hostname = String::new(); + file.read_to_string(&mut hostname).unwrap(); + let mut kernel = String::new(); + file = File::open("/proc/sys/kernel/version").unwrap(); + file.read_to_string(&mut kernel).unwrap(); + let mut os_type = String::new(); + file = File::open("/proc/sys/kernel/ostype").unwrap(); + file.read_to_string(&mut os_type).unwrap(); + let mut boot_time = 0; + let cpu_stat = get_stat(&mut boot_time); + KernelMetrics { + hostname, + kernel, + os_type, + uptime: String::with_capacity(60), + cpu_stat, + boot_time, + dirty: true, + } + } +} + +impl Component for KernelMetrics { + fn draw( + &mut self, + grid: &mut CellBuffer, + area: Area, + dirty_areas: &mut VecDeque<Area>, + tick: bool, + ) { + if !is_valid_area!(area) { + return; + } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + let total_rows = height!(area); + let total_cols = width!(area); + dirty_areas.push_back(area); + if self.dirty { + clear_area(grid, area); + let (x, y) = write_string_to_grid( + &self.hostname, + grid, + Color::Default, + Color::Default, + Attr::Bold, + area, + false, + ); + let (x, y) = write_string_to_grid( + &self.os_type, + grid, + Color::Default, + Color::Default, + Attr::Default, + ((x + 2, y), bottom_right), + false, + ); + let (x, y) = write_string_to_grid( + &self.kernel, + grid, + Color::Default, + Color::Default, + Attr::Default, + ((x + 2, y), bottom_right), + false, + ); + self.dirty = false; + } + + /* Draw uptime */ + let mut file = File::open("/proc/uptime").unwrap(); + self.uptime.clear(); + file.read_to_string(&mut self.uptime).unwrap(); + let seconds: usize = + f64::from_str(self.uptime.split(" ").next().unwrap()).unwrap() as usize; + let days = seconds / (60 * 60 * 24); + let hours = seconds / 3600 - days * 24; + let mins = seconds / 60 - hours * 60 - days * 24 * 60; + let seconds = seconds % 60; + let uptime = if days > 0 { + format!( + "uptime: {} days, {:02}:{:02}:{:02}", + days, hours, mins, seconds + ) + } else { + format!("uptime: {:02}:{:02}:{:02}", hours, mins, seconds) + }; + + write_string_to_grid( + &uptime, + grid, + Color::Default, + Color::Default, + Attr::Default, + ( + (get_x(bottom_right) - uptime.len(), get_y(upper_left)), + bottom_right, + ), + false, + ); + + let mut y_offset = 2; + + if !tick { + return; + } + /* Draw CPU usage bars */ + + let bar_max = std::dbg!((0.6 * total_cols as f32) as usize); + + let mut boot_time: usize = 0; + for (i, cpu_stat) in get_stat(&mut boot_time).into_iter().enumerate() { + let (mut x, y) = write_string_to_grid( + "CPU", + grid, + Color::Byte(250), + Color::Default, + Attr::Default, + (pos_inc(upper_left, (2, 2 + i)), bottom_right), + false, + ); + if i > 0 { + write_string_to_grid( + &i.to_string(), + grid, + Color::Default, + Color::Default, + Attr::Default, + ((x, y), bottom_right), + false, + ); + } else { + /* add padding */ + write_string_to_grid( + " ", + grid, + Color::Default, + Color::Default, + Attr::Default, + ((x, y), bottom_right), + false, + ); + } + x += 2; + + /* Calculate percentages for the cpu usage bar */ + let busy_length = (cpu_stat.user_time + cpu_stat.system_time) + - (self.cpu_stat[i].user_time + self.cpu_stat[i].system_time); + let iowait_length = cpu_stat.iowait_time - self.cpu_stat[i].iowait_time; + let bar_length: usize = std::dbg!( + (((busy_length + iowait_length) as f64 / 100.0) * bar_max as f64) as usize + ); + + let mut x_offset = 0; + while x_offset < bar_length { + write_string_to_grid( + "▁", + grid, + Color::Byte(235), + Color::Byte(240), + Attr::Default, + ((x + x_offset, y), bottom_right), + false, + ); + x_offset += 1; + } + while x_offset < bar_max { + write_string_to_grid( + "▁", + grid, + Color::Byte(236), + Color::Byte(235), + Attr::Default, + ((x + x_offset, y), bottom_right), + false, + ); + + x_offset += 1; + } + self.cpu_stat[i] = cpu_stat; + y_offset += 1; + } + self.boot_time = boot_time; + + /* Draw RAM usage bar */ + + y_offset += 1; + + let bar_max = bar_max + 5; + let (available, total) = get_mem_info(); + let available_length = ((available as f64 / total as f64) * bar_max as f64) as usize; + let mem_bar_length = bar_max - available_length; + let mem_display = format!( + "RAM {}/{}", + Bytes((total - available) * 1024).as_convenient_string(), + Bytes(total * 1024).as_convenient_string() + ); + let mem_display_padding = bar_max.saturating_sub(mem_display.len()) / 2; + + let mut x = 0; + /* Calculate spillover of mem_display string to available part of the bar in order to + * paint it differently */ + let cutoff = if mem_display_padding + mem_display.len() > mem_bar_length { + mem_bar_length - mem_display_padding + } else { + mem_display.len() + }; + + while x < mem_bar_length { + if x == mem_display_padding { + let (_x, _) = write_string_to_grid( + &mem_display[0..cutoff], + grid, + Color::White, + Color::Byte(240), + Attr::Default, + (pos_inc(upper_left, (x + 2, y_offset)), bottom_right), + false, + ); + x += cutoff; + } else { + write_string_to_grid( + "█", + grid, + Color::Byte(240), + Color::Byte(235), + Attr::Default, + (pos_inc(upper_left, (x + 2, y_offset)), bottom_right), + false, + ); + x += 1; + } + } + let x = if cutoff != mem_display.len() { + let (_x, _) = write_string_to_grid( + &mem_display[cutoff..], + grid, + Color::White, + Color::Byte(235), + Attr::Default, + (pos_inc(upper_left, (x + 2, y_offset)), bottom_right), + false, + ); + _x + } else { + x + }; + for x in x..bar_max { + write_string_to_grid( + " ", + grid, + Color::Default, + Color::Byte(235), + Attr::Default, + (pos_inc(upper_left, (x + 2, y_offset)), bottom_right), + false, + ); + } + } + + fn process_event(&mut self, event: &mut UIEvent) { + match event { + UIEvent::Resize => { + self.dirty = true; + } + _ => {} + } + } + + fn is_dirty(&self) -> bool { + true + } + + fn set_dirty(&mut self) { + self.dirty = true; + } +} + +fn get_mem_info() -> (usize, usize) { + let mut file = File::open("/proc/meminfo").unwrap(); + let mut res = String::with_capacity(2048); + file.read_to_string(&mut res).unwrap(); + let mut lines_iter = res.lines(); + let mem_total = usize::from_str( + lines_iter |