summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-08-31 15:37:46 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-08-31 15:37:46 +0300
commit31bf144ecdfa3c1da9e38e1f6338329aab996680 (patch)
treeb0aaaa045905bb78adc6e60af16b867e93c4e371 /src
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/main.rs134
-rw-r--r--src/ui.rs125
-rw-r--r--src/ui/components.rs394
-rw-r--r--src/ui/components/kernel.rs376
-rw-r--r--src/ui/components/processes.rs334
-rw-r--r--src/ui/components/utilities.rs282
-rw-r--r--src/ui/components/utilities/widgets.rs69
-rw-r--r--src/ui/state.rs356
-rw-r--r--src/ui/terminal.rs29
-rw-r--r--src/ui/terminal/cells.rs801
-rw-r--r--src/ui/terminal/keys.rs239
-rw-r--r--src/ui/terminal/position.rs180
-rw-r--r--src/ui/text_processing.rs8
-rw-r--r--src/ui/text_processing/grapheme_clusters.rs1845
-rw-r--r--src/ui/text_processing/line_break.rs703
-rw-r--r--src/ui/text_processing/tables.rs3389
-rw-r--r--src/ui/text_processing/types.rs102
-rw-r--r--src/ui/text_processing/wcwidth.rs661
-rw-r--r--src/ui/types.rs61
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