summaryrefslogtreecommitdiffstats
path: root/src/components/processes.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/processes.rs')
-rw-r--r--src/components/processes.rs2076
1 files changed, 2076 insertions, 0 deletions
diff --git a/src/components/processes.rs b/src/components/processes.rs
new file mode 100644
index 0000000..93cc835
--- /dev/null
+++ b/src/components/processes.rs
@@ -0,0 +1,2076 @@
+/*
+ * bb
+ *
+ * Copyright 2019 Manos Pitsidianakis
+ *
+ * This file is part of bb.
+ *
+ * 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/>.
+ */
+
+use super::*;
+use std::collections::{HashMap, HashSet};
+use std::fs::File;
+use std::io::prelude::*;
+use std::path::PathBuf;
+use std::str::FromStr;
+#[derive(Debug)]
+pub struct ProcessData {
+ cpu_stat: Stat,
+ processes_times: HashMap<Pid, usize>,
+ threads_times: HashMap<Pid, usize>,
+ parents: HashMap<Pid, Vec<Pid>>,
+ processes_index: HashMap<Pid, usize>,
+ tree_index: HashMap<Pid, usize>,
+ tree: Vec<(usize, Pid)>,
+}
+
+const SIGNAL_LIST: &[(i32, &'static str)] = &[
+ (1, "1 HUP"),
+ (2, "2 INT"),
+ (3, "3 QUIT"),
+ (4, "4 ILL"),
+ (5, "5 TRAP"),
+ (6, "6 ABRT"),
+ (7, "7 BUS"),
+ (8, "8 FPE"),
+ (9, "9 KILL"),
+ (10, "10 USR1"),
+ (11, "11 SEGV"),
+ (12, "12 USR2"),
+ (13, "13 PIPE"),
+ (14, "14 ALRM"),
+ (15, "15 TERM"),
+ (16, "16 STKFLT"),
+ (17, "17 CHLD"),
+ (18, "18 CONT"),
+ (19, "19 STOP"),
+ (20, "20 TSTP"),
+ (21, "21 TTIN"),
+ (22, "22 TTOU"),
+ (23, "23 URG"),
+ (24, "24 XCPU"),
+ (25, "25 XFSZ"),
+ (26, "26 VTALRM"),
+ (27, "27 PROF"),
+ (28, "28 WINCH"),
+ (29, "29 POLL"),
+ (30, "30 PWR"),
+ (31, "31 SYS"),
+];
+
+/* Hold maximum width for each column */
+#[derive(Debug)]
+pub struct ColumnWidthMaxima {
+ pid: usize,
+ ppid: usize,
+ vm_rss: usize,
+ cpu_percent: usize,
+ state: usize,
+ username: usize,
+}
+
+impl ColumnWidthMaxima {
+ fn new() -> ColumnWidthMaxima {
+ ColumnWidthMaxima {
+ pid: "PID".len(),
+ ppid: "PPID".len(),
+ vm_rss: "VM_RSS".len(),
+ cpu_percent: " CPU%".len(),
+ state: 1,
+ username: "USER".len(),
+ }
+ }
+}
+
+macro_rules! define_column_string {
+ ($($typename: tt),+) => {
+ $(
+ #[derive(Debug)]
+ pub struct $typename(pub String);
+
+ impl $typename {
+ #[allow(dead_code)]
+ pub fn len(&self) -> usize {
+ self.0.len()
+ }
+ }
+
+ impl std::fmt::Display for $typename {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if let Some(width) = f.width() {
+ write!(f, "{:>width$}", self.0, width = width)
+ } else {
+ write!(f, "{:>}", self.0)
+ }
+ }
+ })+
+ }
+}
+
+define_column_string!(
+ PidString,
+ PpidString,
+ VmRssString,
+ CmdLineString,
+ UserString
+);
+
+pub type Pid = i32;
+
+#[derive(Debug, Copy, Clone)]
+enum Sort {
+ UserAsc,
+ UserDesc,
+ VmRssAsc,
+ VmRssDesc,
+ CpuAsc,
+ CpuDesc,
+ CmdLineAsc,
+ CmdLineDesc,
+}
+
+/* Wrapper type for display strings */
+#[derive(Debug)]
+pub struct ProcessDisplay {
+ pub i: Pid,
+ pub p: Pid,
+ pub pid: PidString,
+ pub ppid: PpidString,
+ pub vm_rss: VmRssString,
+ vm_rss_value: usize,
+ pub cpu_percent: usize,
+ pub state: State,
+ pub cmd_line: CmdLineString,
+ pub username: UserString,
+ pub rtime: usize,
+ pub is_thread: bool,
+}
+
+/* process list components */
+#[derive(Debug)]
+pub struct ProcessList {
+ page_movement: Option<PageMovement>,
+ cpu_stat: Stat,
+ data: ProcessData,
+ cursor: usize,
+ height: usize,
+ dirty: bool,
+ force_redraw: bool,
+ maxima: ColumnWidthMaxima,
+ /* stop updating data */
+ freeze: bool,
+ draw_tree: bool,
+ draw_help: bool,
+ processes_times: HashMap<Pid, usize>,
+ threads_times: HashMap<Pid, usize>,
+ processes: Vec<ProcessDisplay>,
+ sort: Sort,
+ mode: FunctionModes,
+}
+
+#[derive(Debug, PartialEq)]
+enum Input {
+ Active,
+ Inactive,
+}
+
+impl Input {
+ fn set_inactive(&mut self, ui_mode: &mut UIMode) {
+ *self = Inactive;
+ *ui_mode = UIMode::Normal;
+ }
+
+ fn set_active(&mut self, ui_mode: &mut UIMode) {
+ *self = Active;
+ *ui_mode = UIMode::Input;
+ }
+}
+
+impl Default for Input {
+ fn default() -> Self {
+ Inactive
+ }
+}
+use Input::*;
+
+const FOLLOW_ACTIVE: u8 = 0x01;
+const LOCATE_ACTIVE: u8 = 0x02;
+const SEARCH_ACTIVE: u8 = 0x04;
+const KILL_ACTIVE: u8 = 0x08;
+const HELP_ACTIVE: u8 = 0x10;
+const FILTER_ACTIVE: u8 = 0x40;
+
+#[derive(Debug, PartialEq, Default)]
+struct FunctionModes {
+ follow: Pid,
+ locate: Pid,
+ search: (String, usize, usize, bool), /* (search term, current result y_offset, previous result offset, is cursor focused on results?) */
+ kill: u16,
+ filter: String,
+ active: u8,
+ input: Input,
+}
+
+impl FunctionModes {
+ #[inline(always)]
+ fn is_active(&self, mask: u8) -> bool {
+ (self.active & mask) > 0
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum State {
+ /* Z Zombie */
+ Zombie,
+ /* R Running */
+ Running,
+ /* S Sleeping in an interruptible wait */
+ Sleeping,
+ /* D Waiting in uninterruptible disk sleep */
+ Waiting,
+ /* T Stopped (on a signal) or (before Linux 2.6.33) trace stopped */
+ Stopped,
+ /* t Tracing stop (Linux 2.6.33 onward) */
+ Tracing,
+ /* X Dead (from Linux 2.6.0 onward) */
+ Dead,
+}
+
+impl From<char> for State {
+ fn from(val: char) -> State {
+ match val {
+ 'R' => State::Running,
+ 'I' => State::Sleeping,
+ 'S' => State::Sleeping,
+ 'D' => State::Waiting,
+ 'Z' => State::Zombie,
+ 'T' => State::Stopped,
+ 't' => State::Tracing,
+ 'X' => State::Dead,
+ 'x' => State::Dead,
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl std::fmt::Display for State {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ State::Running => 'R',
+ State::Sleeping => 'S',
+ State::Waiting => 'D',
+ State::Zombie => 'Z',
+ State::Stopped => 'T',
+ State::Tracing => 't',
+ State::Dead => 'X',
+ }
+ )
+ }
+}
+
+pub struct Process {
+ pub pid: i32,
+ pub ppid: i32,
+ pub vm_rss: usize,
+ pub state: State,
+ pub uid: u32,
+ pub cmd_line: String,
+ pub rtime: usize,
+}
+
+impl fmt::Display for ProcessList {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "process list")
+ }
+}
+
+impl ProcessList {
+ pub fn new() -> Self {
+ let data = ProcessData {
+ cpu_stat: get_stat(&mut 0).remove(0),
+ processes_times: Default::default(),
+ threads_times: Default::default(),
+ processes_index: Default::default(),
+ tree_index: Default::default(),
+ parents: Default::default(),
+ tree: Default::default(),
+ };
+ ProcessList {
+ cursor: 0,
+ page_movement: None,
+ cpu_stat: get_stat(&mut 0).remove(0),
+ data,
+ processes: Vec::with_capacity(1024),
+ processes_times: Default::default(),
+ threads_times: Default::default(),
+ height: 0,
+ maxima: ColumnWidthMaxima::new(),
+ freeze: false,
+ draw_tree: false,
+ draw_help: false,
+ mode: FunctionModes::default(),
+ dirty: true,
+ sort: Sort::CpuDesc,
+ force_redraw: false,
+ }
+ }
+
+ fn follow(&self) -> Option<Pid> {
+ if self.mode.is_active(FOLLOW_ACTIVE) {
+ Some(self.mode.follow)
+ } else {
+ None
+ }
+ }
+
+ fn get_pid_under_cursor(&self, cursor: usize) -> Pid {
+ if self.draw_tree {
+ self.data.tree[cursor].1
+ } else {
+ let mut processes = self.processes.iter().collect::<Vec<&ProcessDisplay>>();
+ processes.sort_unstable_by(|a, b| match self.sort {
+ Sort::CpuAsc => a.cpu_percent.cmp(&b.cpu_percent),
+ Sort::CpuDesc => b.cpu_percent.cmp(&a.cpu_percent),
+ Sort::VmRssAsc => a.vm_rss_value.cmp(&b.vm_rss_value),
+ Sort::VmRssDesc => b.vm_rss_value.cmp(&a.vm_rss_value),
+ Sort::UserAsc => a.username.0.cmp(&b.username.0),
+ Sort::UserDesc => b.username.0.cmp(&a.username.0),
+ Sort::CmdLineAsc => a.cmd_line.0.cmp(&b.cmd_line.0),
+ Sort::CmdLineDesc => b.cmd_line.0.cmp(&a.cmd_line.0),
+ });
+ if self.mode.is_active(FILTER_ACTIVE) {
+ processes.retain(|process| process.cmd_line.0.contains(self.mode.filter.as_str()));
+ }
+ processes[cursor].i
+ }
+ }
+
+ fn draw_help_box(&self, grid: &mut CellBuffer) {
+ let (cols, rows) = grid.size();
+ let margin_left = (cols / 2).saturating_sub(20);
+ let margin_top = (rows / 2).saturating_sub(12);
+ let box_area = (
+ (margin_left, margin_top),
+ (margin_left + 36, margin_top + 12),
+ );
+ clear_area(grid, box_area);
+ create_box(grid, box_area);
+ let shortcuts_map = &self.get_shortcuts()[""];
+ let max_key = shortcuts_map.keys().map(|k| k.len()).max().unwrap();
+ let mut shortcuts = shortcuts_map
+ .iter()
+ .map(|(k, v)| (*k, v))
+ .collect::<Vec<(&str, &Key)>>();
+ shortcuts.sort_by_key(|s| s.0);
+ let mut y = 0;
+ for (k, v) in shortcuts.iter() {
+ write_string_to_grid(
+ &format!("{k:>max_key$} {v}", k = k, v = v, max_key = max_key),
+ grid,
+ Color::Default,
+ Color::Default,
+ Attr::Default,
+ (
+ pos_inc(upper_left!(box_area), (2, 4 + y)),
+ bottom_right!(box_area),
+ ),
+ None,
+ );
+ y += 1;
+ }
+ let box_area = (
+ (margin_left, margin_top + 13),
+ (margin_left + 36, margin_top + 16),
+ );
+ clear_area(grid, box_area);
+ create_box(grid, box_area);
+ }
+
+ fn draw_tree_list(
+ &mut self,
+ grid: &mut CellBuffer,
+ area: Area,
+ tick: bool,
+ mut pages: usize,
+ height: usize,
+ ) {
+ let (upper_left, bottom_right) = area;
+
+ let mut y_offset = 0;
+
+ let mut branches = vec![];
+ let mut child_counters = vec![0];
+
+ self.cursor = std::cmp::min(self.height, self.cursor);
+ let mut lines = Vec::with_capacity(2048);
+ let tree = self
+ .data
+ .tree
+ .iter()
+ .filter(|(_, pid)| {
+ if self.mode.is_active(FILTER_ACTIVE) {
+ self.processes[self.data.processes_index[pid]]
+ .cmd_line
+ .0
+ .contains(self.mode.filter.as_str())
+ } else {
+ true
+ }
+ })
+ .collect::<Vec<&_>>();
+ let mut iter = tree.iter().peekable();
+
+ let mut stop_search = false;
+ let mut search_results = 0;
+ while let Some((ind, pid)) = iter.next() {
+ let p = &self.processes[self.data.processes_index[pid]];
+ if self.mode.is_active(SEARCH_ACTIVE) {
+ let (ref search, ref mut n, ref mut n_prev, is_cursor_focused) = self.mode.search;
+ if (*n != *n_prev || tick) && is_cursor_focused {
+ if !stop_search && search.len() > 1 && p.cmd_line.0.contains(search) {
+ if search_results == *n {
+ let i = y_offset;
+ pages = i / height;
+ self.cursor = i;
+ stop_search = true;
+ *n_prev = *n;
+ } else {
+ search_results += 1;
+ }
+ if search_results < *n {
+ *n = search_results;
+ }
+ }
+ }
+ }
+ let has_sibling: bool = child_counters
+ .get(*ind)
+ .map(|n| {
+ *n != self
+ .data
+ .parents
+ .get(&p.p)
+ .and_then(|v| Some(v.len() - 1))
+ .unwrap_or(0)
+ })
+ .unwrap_or(false);
+
+ let is_first: bool = child_counters.last().map(|n| *n == 0).unwrap_or(false);
+
+ if let Some(counter) = child_counters.get_mut(*ind) {
+ *counter += 1;
+ }
+
+ let mut s = String::with_capacity(16);
+
+ for i in 0..*ind {
+ if branches.len() > i && branches[i] {
+ s.push(' ');
+ s.push('┃');
+ } else {
+ s.push(' ');
+ }
+ if i > 0 {}
+ s.push(' ');
+ }
+
+ if *ind > 0 || (has_sibling || is_first) {
+ if p.is_thread {
+ if has_sibling && is_first {
+ s.push(' ');
+ s.push('┠');
+ } else if has_sibling {
+ s.push(' ');
+ s.push('┠');
+ } else {
+ s.push(' ');
+ s.push('┖');
+ }
+ s.push('┄');
+ } else {
+ if has_sibling && is_first {
+ s.push(' ');
+ s.push('┣');
+ } else if has_sibling {
+ s.push(' ');
+ s.push('┣');
+ } else {
+ s.push(' ');
+ s.push('┗');
+ }
+ s.push('━');
+ }
+ s.push('>');
+ }
+
+ lines.push(s);
+ match iter.peek() {
+ Some((n, _)) if *n > *ind => {
+ child_counters.push(0);
+ if has_sibling {
+ branches.push(true);
+ } else {
+ branches.push(false);
+ }
+ }
+ Some((n, _)) if *n < *ind => {
+ for _ in 0..(ind - *n) {
+ branches.pop();
+ child_counters.pop();
+ }
+ }
+ _ => {}
+ }
+ }
+
+ for ((_, pid), s) in tree
+ .iter()
+ .zip(lines.iter())
+ .skip(pages * height)
+ .take(height)
+ {
+ let (fg_color, bg_color) = if pages * height + y_offset == self.cursor {
+ (Color::White, Color::Byte(235))
+ } else if self.mode.is_active(LOCATE_ACTIVE) {
+ let highlighted_pid = self.mode.locate;
+ if highlighted_pid == *pid {
+ (Color::Red, Color::Yellow)
+ } else {
+ (Color::Default, Color::Default)
+ }
+ } else {
+ (Color::Default, Color::Default)
+ };
+
+ let p = &self.processes[self.data.processes_index[pid]];
+ match executable_path_color(&p.cmd_line) {
+ Ok((path, bin, rest)) => {
+ let (x, _) = write_string_to_grid(
+ &format!(
+ "{pid:>max_pid$} {ppid:>max_ppid$} {username:>max_username$} {vm_rss:>max_vm_rss$} {cpu_percent:>max_cpu_percent$}% {state:>max_state$} {branches}",
+ pid = p.pid,
+ ppid = p.ppid,
+ username = p.username,
+ vm_rss = p.vm_rss,
+ cpu_percent = (p.cpu_percent as f32 / 1000.0),
+ state = p.state,
+ max_pid = self.maxima.pid,
+ max_ppid = self.maxima.ppid,
+ max_username = self.maxima.username,
+ max_vm_rss = self.maxima.vm_rss,
+ max_cpu_percent = self.maxima.cpu_percent,
+ max_state = self.maxima.state,
+ branches = s,
+ ),
+ grid,
+ fg_color,
+ bg_color,
+ Attr::Default,
+ (pos_inc(upper_left, (0, y_offset + 2)), bottom_right),
+ None,);
+ if p.state == State::Running {
+ grid[pos_inc(
+ upper_left,
+ (
+ 2 * 5
+ + self.maxima.pid
+ + self.maxima.ppid
+ + self.maxima.username
+ + self.maxima.vm_rss
+ + self.maxima.cpu_percent,
+ y_offset + 2,
+ ),
+ )]
+ .set_fg(if self.freeze {
+ Color::Byte(12)
+ } else {
+ Color::Byte(10)
+ });
+ }
+ let (x, _) = write_string_to_grid(
+ path,
+ grid,
+ Color::Byte(243),
+ bg_color,
+ Attr::Default,
+ (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right),
+ None,
+ );
+ let (x, _) = write_string_to_grid(
+ bin,
+ grid,
+ if self.freeze {
+ Color::Byte(32)
+ } else {
+ Color::Byte(34)
+ },
+ bg_color,
+ Attr::Default,
+ (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right),
+ None,
+ );
+ write_string_to_grid(
+ rest,
+ grid,
+ fg_color,
+ bg_color,
+ Attr::Default,
+ (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right),
+ None,
+ );
+ change_colors(
+ grid,
+ (
+ pos_inc(upper_left, (x - 1 + rest.len(), y_offset + 2)),
+ set_y(bottom_right, get_y(upper_left) + y_offset + 2),
+ ),
+ Some(fg_color),
+ Some(bg_color),
+ );
+ }
+ Err((bin, rest)) => {
+ let (x, _) = write_string_to_grid(
+ &format!(
+ "{pid:>max_pid$} {ppid:>max_ppid$} {username:>max_username$} {vm_rss:>max_vm_rss$} {cpu_percent:>max_cpu_percent$}% {state:>max_state$} {branches}",
+ pid = p.pid,
+ ppid = p.ppid,
+ username = p.username,
+ vm_rss = p.vm_rss,
+ cpu_percent = (p.cpu_percent as f32 / 100.0),
+ state = p.state,
+ max_pid = self.maxima.pid,
+ max_ppid = self.maxima.ppid,
+ max_username = self.maxima.username,
+ max_vm_rss = self.maxima.vm_rss,
+ max_cpu_percent = self.maxima.cpu_percent,
+ max_state = self.maxima.state,
+ branches = s,
+ ),
+ grid,
+ fg_color,
+ bg_color,
+ Attr::Default,
+ (pos_inc(upper_left, (0, y_offset + 2)), bottom_right),
+ None,);
+ if p.state == State::Running {
+ grid[pos_inc(
+ upper_left,
+ (
+ 2 * 5
+ + 1
+ + self.maxima.pid
+ + self.maxima.ppid
+ + self.maxima.username
+ + self.maxima.vm_rss
+ + self.maxima.cpu_percent,
+ y_offset + 2,
+ ),
+ )]
+ .set_fg(if self.freeze {
+ Color::Byte(12)
+ } else {
+ Color::Byte(10)
+ });
+ }
+ let (x, _) = write_string_to_grid(
+ bin,
+ grid,
+ if self.freeze {
+ Color::Byte(32)
+ } else {
+ Color::Byte(34)
+ },
+ bg_color,
+ Attr::Default,
+ (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right),
+ None,
+ );
+ write_string_to_grid(
+ rest,
+ grid,
+ fg_color,
+ bg_color,
+ Attr::Default,
+ (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right),
+ None,
+ );
+ change_colors(
+ grid,
+ (
+ pos_inc(upper_left, (x - 1 + rest.len(), y_offset + 2)),
+ set_y(bottom_right, get_y(upper_left) + y_offset + 2),
+ ),
+ Some(fg_color),
+ Some(bg_color),
+ );
+ }
+ }
+ y_offset += 1;
+ }
+ }
+}
+
+/* Prints the process list */
+impl Component for ProcessList {
+ fn draw(
+ &mut self,
+ grid: &mut CellBuffer,
+ area: Area,
+ dirty_areas: &mut VecDeque<Area>,
+ mut tick: bool,
+ ) {
+ if !is_valid_area!(area) {
+ return;
+ }
+
+ if self.force_redraw {
+ self.force_redraw = false;
+ tick = true;
+ }
+
+ let mut upper_left = pos_inc(upper_left!(area), (1, 0));
+ let bottom_right = pos_dec(bottom_right!(area), (1, 1));
+
+ /* Reserve first row for column headers */
+ let height = height!(area)
+ - 2
+ - if self.mode.is_active(LOCATE_ACTIVE) {
+ 2
+ } else {
+ 0
+ };
+ let width = width!(area);
+ let old_pages = (self.cursor) / height;
+
+ let old_cursor = self.cursor;
+ if let Some(mvm) = self.page_movement.take() {
+ match mvm {
+ PageMovement::Up => {
+ self.cursor = self.cursor.saturating_sub(1);
+ }
+ PageMovement::Down => {
+ self.cursor = std::cmp::min(self.height.saturating_sub(1), self.cursor + 1);
+ }
+ PageMovement::Home => {
+ self.cursor = 0;
+ }
+ PageMovement::PageUp => {
+ self.cursor = self.cursor.saturating_sub(height);
+ }
+ PageMovement::PageDown => {
+ self.cursor =
+ std::cmp::min(self.height.saturating_sub(1), self.cursor + height);
+ }
+ PageMovement::End => {
+ self.cursor = self.height.saturating_sub(1);
+ }
+ }
+ }
+
+ let mut pages = (self.cursor) / height;
+ if pages != old_pages {
+ tick = true;
+ }
+
+ let update_maxima = tick && !self.freeze;
+
+ if update_maxima {
+ let follow = self.follow();
+ self.processes = get(&mut self.data, follow, self.sort);
+ };
+
+ if tick || self.freeze {
+ if tick || old_cursor != self.cursor {
+ clear_area(grid, area);
+ }
+
+ dirty_areas.push_back(area);
+
+ if !self.freeze && update_maxima {
+ /* Keep tabs on biggest element in each column */
+ self.maxima = ColumnWidthMaxima::new();
+
+ for p in &self.processes {
+ self.maxima.pid = std::cmp::max(self.maxima.pid, p.pid.len());
+ self.maxima.ppid = std::cmp::max(self.maxima.ppid, p.ppid.len());
+ self.maxima.vm_rss = std::cmp::max(self.maxima.vm_rss, p.vm_rss.len());
+ self.maxima.username = std::cmp::max(self.maxima.username, p.username.len());
+ }
+
+ self.cursor = std::cmp::min(self.height.saturating_sub(1), self.cursor);
+ }
+ if self.mode.is_active(FOLLOW_ACTIVE) {
+ let pid = self.mode.follow;
+ let info = format!("Following PID == {pid} || PPID == {pid}", pid = pid);
+ write_string_to_grid(
+ &info,
+ grid,
+ Color::Default,
+ Color::Default,
+ Attr::Bold,
+ (
+ pos_inc(
+ upper_left!(area),
+ ((width / 2).saturating_sub(info.len() / 2), 1),
+ ),
+ bottom_right!(area),
+ ),
+ None,
+ );
+ upper_left = pos_inc(upper_left, (0, 2));
+ }
+ if self.mode.is_active(LOCATE_ACTIVE) {
+ let pid = &self.mode.locate;
+ let info = format!("Highlighting PID == {pid}", pid = pid);
+ write_string_to_grid(
+ &info,
+ grid,
+ Color::Default,
+ Color::Default,
+ Attr::Bold,
+ (
+ pos_inc(
+ upper_left!(area),
+ ((width / 2).saturating_sub(info.len() / 2), 1),
+ ),
+ bottom_right!(area),
+ ),
+ None,
+ );
+ upper_left = pos_inc(upper_left, (0, 2));
+ }
+
+ let cmd_header = if self.mode.is_active(SEARCH_ACTIVE) {
+ let (ref p, _, _, _) = self.mode.search;
+ Some(format!("CMD_LINE (search: {})", p))
+ } else if self.mode.is_active(FILTER_ACTIVE) {
+ Some(format!("CMD_LINE (filter: {})", &self.mode.filter))
+ } else {
+ None
+ };
+
+ /* Write column headers */
+ let (x, y) = write_string_to_grid(
+ &format!(
+ "{pid:>max_pid$} {ppid:>max_ppid$} {username:>max_username$}{usernamesort} {vm_rss:>max_vm_rss$}{vmrsssort} {cpu_percent:>max_cpu_percent$}{cpusort} {state:>max_state$} {cmd_line}{cmd_linesort}",
+ pid = "PID",
+ ppid ="PPID",
+ username = "USER",
+ vm_rss = "VM_RSS",
+ cpu_percent = " CPU%",
+ state = " ",
+ cmd_line = if let Some(ref cmd_header) = cmd_header { &cmd_header } else { "CMD_LINE"} ,
+ max_pid = self.maxima.pid,
+ max_ppid = self.maxima.ppid,
+ max_username = self.maxima.username,
+ max_vm_rss = self.maxima.vm_rss,
+ max_cpu_percent = self.maxima.cpu_percent,
+ max_state = self.maxima.state,
+ usernamesort = if let Sort::UserAsc = self.sort { "↑" } else if let Sort::UserDesc = self.sort { "↓" } else { " " },
+ vmrsssort = if let Sort::VmRssAsc = self.sort { "↑" } else if let Sort::VmRssDesc = self.sort { "↓" } else { " " },
+ cpusort = if let Sort::CpuAsc = self.sort { "↑" } else if let Sort::CpuDesc = self.sort { "↓" } else { " " },
+ cmd_linesort = if let Sort::CmdLineAsc = self.sort { "↑" } else if let Sort::CmdLineDesc = self.sort { "↓" } else { "" },
+ ),
+ grid,
+ Color::Black,
+ Color::White,
+ Attr::Default,
+ (pos_inc(upper_left, (0, 1)), bottom_right),
+ None,);
+ if self.mode.is_active(SEARCH_ACTIVE | FILTER_ACTIVE) && self.mode.input == Active {
+ grid[(x - 1, y)].set_fg(Color::White);
+ grid[(x - 1, y)].set_bg(Color::Black);
+ }
+ change_colors(
+ grid,
+ ((x, y), set_y(bottom_right, y)),
+ Some(Color::Black),
+ Some(Color::White),
+ );
+
+ /* Write current selected status if any. eg. if list is frozen, show 'FROZEN'. */
+ {
+ let (mut x, y) = set_y(upper_left!(area), get_y(bottom_right) + 1);
+ if self.freeze {
+ let (_x, _) = write_string_to_grid(
+ " FROZEN ",
+ grid,
+ Color::White,
+ Color::Byte(26), // DodgerBlue3
+ Attr::Bold,
+ ((x, y), pos_inc(bottom_right, (0, 1))),
+ None,
+ );
+ x = _x;
+ }
+
+ if self.mode.is_active(SEARCH_ACTIVE) {
+ let (_x, _) = write_string_to_grid(
+ " SEARCH ",
+ grid,
+ Color::White,
+ Color::Byte(88), // DarkRed
+ Attr::Bold,
+ ((x, y), pos_inc(bottom_right, (0, 1))),
+ None,
+ );
+ x = _x;
+ }
+ if self.mode.is_active(FOLLOW_ACTIVE) {
+ let (_x, _) = write_string_to_grid(
+ " FOLLOW ",
+ grid,
+ Color::White,
+ Color::Byte(172), // Orange3
+ Attr::Bold,
+ ((x, y), pos_inc(bottom_right, (0, 1))),
+ None,
+ );
+ x = _x;
+ }
+ if self.mode.is_active(LOCATE_ACTIVE) {
+ let (_x, _) = write_string_to_grid(
+ " LOCATE ",
+ grid,
+ Color::White,
+ Color::Green,
+ Attr::Bold,
+ ((x, y), pos_inc(bottom_right, (0, 1))),
+ None,
+ );
+ x = _x;
+ }
+ if self.mode.is_active(KILL_ACTIVE) {
+ let (_x, _) = write_string_to_grid(
+ " KILL ",
+ grid,
+ Color::White,
+ Color::Byte(8), // Grey
+ Attr::Bold,
+ ((x, y), pos_inc(bottom_right, (0, 1))),
+ None,
+ );
+ x = _x;
+ }
+
+ if self.mode.is_active(FILTER_ACTIVE) {
+ write_string_to_grid(
+ " FILTER ",
+ grid,
+ Color::White,
+ Color::Byte(13), // Fuschia
+ Attr::Bold,
+ ((x, y), pos_inc(bottom_right, (0, 1))),
+ None,
+ );
+ }
+ }
+ let mut y_offset = 0;
+
+ if self.draw_tree {
+ self.draw_tree_list(grid, (upper_left, bottom_right), tick, pages, height);
+ self.height = self.data.tree.len();
+ self.cursor = std::cmp::min(self.height, self.cursor);
+ } else {
+ let mut processes = self.processes.iter().collect::<Vec<&ProcessDisplay>>();
+ processes.sort_unstable_by(|a, b| match self.sort {
+ Sort::CpuAsc => a.cpu_percent.cmp(&b.cpu_percent),
+ Sort::CpuDesc => b.cpu_percent.cmp(&a.cpu_percent),
+ Sort::VmRssAsc => a.vm_rss_value.cmp(&b.vm_rss_value),
+ Sort::VmRssDesc => b.vm_rss_value.cmp(&a.vm_rss_value),
+ Sort::UserAsc => a.username.0.cmp(&b.username.0),
+ Sort::UserDesc => b.username.0.cmp(&a.username.0),
+ Sort::CmdLineAsc => a.cmd_line.0.cmp(&b.cmd_line.0),
+ Sort::CmdLineDesc => b.cmd_line.0.cmp(&a.cmd_line.0),
+ });
+ if self.mode.is_active(FILTER_ACTIVE) {
+ processes
+ .retain(|process| process.cmd_line.0.contains(self.mode.filter.as_str()));
+ }
+ self.height = processes.len();
+
+ self.cursor = if self.mode.is_active(SEARCH_ACTIVE) && self.mode.search.3 {
+ let (ref search, ref mut n, ref mut n_prev, _) = self.mode.search;
+ if *n != *n_prev || tick {
+ let mut ret = self.cursor;
+ if search.len() > 1 {
+ let mut ctr = 0;
+ for (i, p) in processes.iter().enumerate() {
+ if p