feature: add full command to process widget

This PR adds the ability to toggle between the process name and process path. Currently, this uses `P` as the modifier key.

Currently, the longer command names are dealt with by forcefully changing the width of the columns, but this can be handled in a more graceful manner IMO.
This commit is contained in:
Clement Tsang 2020-08-07 01:29:20 -07:00 committed by GitHub
parent d2129056e3
commit 30bdaa6073
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 330 additions and 207 deletions

View File

@ -8,12 +8,15 @@
"Nonexhaustive", "Nonexhaustive",
"Qudsi", "Qudsi",
"Tebibytes", "Tebibytes",
"Ungrouped",
"Wojnarowski", "Wojnarowski",
"andys", "andys",
"crossterm", "crossterm",
"curr", "curr",
"czvf",
"gotop", "gotop",
"gtop", "gtop",
"haase",
"heim", "heim",
"hjkl", "hjkl",
"markdownlint", "markdownlint",

4
Cargo.lock generated
View File

@ -1340,9 +1340,9 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]] [[package]]
name = "tui" name = "tui"
version = "0.9.4" version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c25eac88406f384894aa6db56ac0378c767254ee5829824ce5b0fc8fd24d5778" checksum = "9533d39bef0ae8f510e8a99d78702e68d1bbf0b98a78ec9740509d287010ae1e"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cassowary", "cassowary",

View File

@ -206,15 +206,16 @@ Run using `btm`.
#### Process bindings #### Process bindings
| | | | | |
| ------------- | ---------------------------------------------------------- | | ------------- | ------------------------------------------------------------- |
| `dd` | Kill the selected process | | `dd` | Kill the selected process |
| `c` | Sort by CPU usage, press again to reverse sorting order | | `c` | Sort by CPU usage, press again to reverse sorting order |
| `m` | Sort by memory usage, press again to reverse sorting order | | `m` | Sort by memory usage, press again to reverse sorting order |
| `p` | Sort by PID name, press again to reverse sorting order | | `p` | Sort by PID name, press again to reverse sorting order |
| `n` | Sort by process name, press again to reverse sorting order | | `n` | Sort by process name, press again to reverse sorting order |
| `Tab` | Group/un-group processes with the same name | | `Tab` | Group/un-group processes with the same name |
| `Ctrl-f`, `/` | Open process search widget | | `Ctrl-f`, `/` | Open process search widget |
| `P` | Toggle between showing the full path or just the process name |
#### Process search bindings #### Process search bindings
@ -551,6 +552,7 @@ Thanks to all contributors ([emoji key](https://allcontributors.org/docs/en/emoj
<!-- markdownlint-enable --> <!-- markdownlint-enable -->
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END --> <!-- ALL-CONTRIBUTORS-LIST:END -->
## Thanks ## Thanks

View File

@ -149,8 +149,7 @@ impl App {
BottomWidgetType::Proc => { BottomWidgetType::Proc => {
if let Some(current_proc_state) = self if let Some(current_proc_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
if current_proc_state.is_search_enabled() { if current_proc_state.is_search_enabled() {
current_proc_state current_proc_state
@ -164,8 +163,7 @@ impl App {
BottomWidgetType::ProcSearch => { BottomWidgetType::ProcSearch => {
if let Some(current_proc_state) = self if let Some(current_proc_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id - 1)
.get_mut(&(self.current_widget.widget_id - 1))
{ {
if current_proc_state.is_search_enabled() { if current_proc_state.is_search_enabled() {
current_proc_state current_proc_state
@ -243,8 +241,7 @@ impl App {
BottomWidgetType::Cpu => { BottomWidgetType::Cpu => {
if let Some(cpu_widget_state) = self if let Some(cpu_widget_state) = self
.cpu_state .cpu_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
cpu_widget_state.is_multi_graph_mode = cpu_widget_state.is_multi_graph_mode =
!cpu_widget_state.is_multi_graph_mode; !cpu_widget_state.is_multi_graph_mode;
@ -253,8 +250,7 @@ impl App {
BottomWidgetType::Proc => { BottomWidgetType::Proc => {
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
// Toggles process widget grouping state // Toggles process widget grouping state
proc_widget_state.is_grouped = !(proc_widget_state.is_grouped); proc_widget_state.is_grouped = !(proc_widget_state.is_grouped);
@ -285,8 +281,7 @@ impl App {
// Toggle on // Toggle on
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
proc_widget_state proc_widget_state
.process_search_state .process_search_state
@ -500,12 +495,20 @@ impl App {
pub fn on_left_key(&mut self) { pub fn on_left_key(&mut self) {
if !self.is_in_dialog() { if !self.is_in_dialog() {
match self.current_widget.widget_type { match self.current_widget.widget_type {
BottomWidgetType::Proc => {
if let Some(proc_widget_state) = self
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
proc_widget_state.current_column_index =
proc_widget_state.current_column_index.saturating_sub(1);
}
}
BottomWidgetType::ProcSearch => { BottomWidgetType::ProcSearch => {
let is_in_search_widget = self.is_in_search_widget(); let is_in_search_widget = self.is_in_search_widget();
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id - 1)
.get_mut(&(self.current_widget.widget_id - 1))
{ {
if is_in_search_widget { if is_in_search_widget {
let prev_cursor = proc_widget_state.get_cursor_position(); let prev_cursor = proc_widget_state.get_cursor_position();
@ -533,8 +536,7 @@ impl App {
if !self.canvas_data.battery_data.is_empty() { if !self.canvas_data.battery_data.is_empty() {
if let Some(battery_widget_state) = self if let Some(battery_widget_state) = self
.battery_state .battery_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
if battery_widget_state.currently_selected_battery_index > 0 { if battery_widget_state.currently_selected_battery_index > 0 {
battery_widget_state.currently_selected_battery_index -= 1; battery_widget_state.currently_selected_battery_index -= 1;
@ -552,12 +554,21 @@ impl App {
pub fn on_right_key(&mut self) { pub fn on_right_key(&mut self) {
if !self.is_in_dialog() { if !self.is_in_dialog() {
match self.current_widget.widget_type { match self.current_widget.widget_type {
BottomWidgetType::Proc => {
if let Some(proc_widget_state) = self
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
if proc_widget_state.current_column_index < proc_widget_state.num_columns {
proc_widget_state.current_column_index += 1;
}
}
}
BottomWidgetType::ProcSearch => { BottomWidgetType::ProcSearch => {
let is_in_search_widget = self.is_in_search_widget(); let is_in_search_widget = self.is_in_search_widget();
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id - 1)
.get_mut(&(self.current_widget.widget_id - 1))
{ {
if is_in_search_widget { if is_in_search_widget {
let prev_cursor = proc_widget_state.get_cursor_position(); let prev_cursor = proc_widget_state.get_cursor_position();
@ -586,8 +597,7 @@ impl App {
let battery_count = self.canvas_data.battery_data.len(); let battery_count = self.canvas_data.battery_data.len();
if let Some(battery_widget_state) = self if let Some(battery_widget_state) = self
.battery_state .battery_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
if battery_widget_state.currently_selected_battery_index if battery_widget_state.currently_selected_battery_index
< battery_count - 1 < battery_count - 1
@ -887,8 +897,7 @@ impl App {
if let BottomWidgetType::Proc = self.current_widget.widget_type { if let BottomWidgetType::Proc = self.current_widget.widget_type {
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
match proc_widget_state.process_sorting_type { match proc_widget_state.process_sorting_type {
processes::ProcessSorting::CPU => { processes::ProcessSorting::CPU => {
@ -911,8 +920,7 @@ impl App {
if let BottomWidgetType::Proc = self.current_widget.widget_type { if let BottomWidgetType::Proc = self.current_widget.widget_type {
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
match proc_widget_state.process_sorting_type { match proc_widget_state.process_sorting_type {
processes::ProcessSorting::MEM => { processes::ProcessSorting::MEM => {
@ -934,8 +942,7 @@ impl App {
if let BottomWidgetType::Proc = self.current_widget.widget_type { if let BottomWidgetType::Proc = self.current_widget.widget_type {
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
// Skip if grouped // Skip if grouped
if !proc_widget_state.is_grouped { if !proc_widget_state.is_grouped {
@ -956,21 +963,32 @@ impl App {
} }
} }
} }
'P' => {
if let BottomWidgetType::Proc = self.current_widget.widget_type {
if let Some(proc_widget_state) = self
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
proc_widget_state.is_using_full_path =
!proc_widget_state.is_using_full_path;
self.proc_state.force_update = Some(self.current_widget.widget_id);
}
}
}
'n' => { 'n' => {
if let BottomWidgetType::Proc = self.current_widget.widget_type { if let BottomWidgetType::Proc = self.current_widget.widget_type {
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
match proc_widget_state.process_sorting_type { match proc_widget_state.process_sorting_type {
processes::ProcessSorting::NAME => { processes::ProcessSorting::IDENTIFIER => {
proc_widget_state.process_sorting_reverse = proc_widget_state.process_sorting_reverse =
!proc_widget_state.process_sorting_reverse !proc_widget_state.process_sorting_reverse
} }
_ => { _ => {
proc_widget_state.process_sorting_type = proc_widget_state.process_sorting_type =
processes::ProcessSorting::NAME; processes::ProcessSorting::IDENTIFIER;
proc_widget_state.process_sorting_reverse = false; proc_widget_state.process_sorting_reverse = false;
} }
} }
@ -1371,8 +1389,7 @@ impl App {
if let Some(new_widget) = self.widget_map.get(&new_widget_id) { if let Some(new_widget) = self.widget_map.get(&new_widget_id) {
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_widget_state(self.current_widget.widget_id)
.get(&self.current_widget.widget_id)
{ {
if proc_widget_state.is_search_enabled() { if proc_widget_state.is_search_enabled() {
self.current_widget = new_widget.clone(); self.current_widget = new_widget.clone();
@ -1393,8 +1410,7 @@ impl App {
BottomWidgetType::Proc => { BottomWidgetType::Proc => {
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
proc_widget_state.scroll_state.current_scroll_position = 0; proc_widget_state.scroll_state.current_scroll_position = 0;
proc_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; proc_widget_state.scroll_state.scroll_direction = ScrollDirection::UP;
@ -1403,8 +1419,7 @@ impl App {
BottomWidgetType::Temp => { BottomWidgetType::Temp => {
if let Some(temp_widget_state) = self if let Some(temp_widget_state) = self
.temp_state .temp_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
temp_widget_state.scroll_state.current_scroll_position = 0; temp_widget_state.scroll_state.current_scroll_position = 0;
temp_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; temp_widget_state.scroll_state.scroll_direction = ScrollDirection::UP;
@ -1413,8 +1428,7 @@ impl App {
BottomWidgetType::Disk => { BottomWidgetType::Disk => {
if let Some(disk_widget_state) = self if let Some(disk_widget_state) = self
.disk_state .disk_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
disk_widget_state.scroll_state.current_scroll_position = 0; disk_widget_state.scroll_state.current_scroll_position = 0;
disk_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; disk_widget_state.scroll_state.scroll_direction = ScrollDirection::UP;
@ -1423,8 +1437,7 @@ impl App {
BottomWidgetType::CpuLegend => { BottomWidgetType::CpuLegend => {
if let Some(cpu_widget_state) = self if let Some(cpu_widget_state) = self
.cpu_state .cpu_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id - 1)
.get_mut(&(self.current_widget.widget_id - 1))
{ {
cpu_widget_state.scroll_state.current_scroll_position = 0; cpu_widget_state.scroll_state.current_scroll_position = 0;
cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::UP; cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::UP;
@ -1445,8 +1458,7 @@ impl App {
BottomWidgetType::Proc => { BottomWidgetType::Proc => {
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
if let Some(finalized_process_data) = self if let Some(finalized_process_data) = self
.canvas_data .canvas_data
@ -1465,8 +1477,7 @@ impl App {
BottomWidgetType::Temp => { BottomWidgetType::Temp => {
if let Some(temp_widget_state) = self if let Some(temp_widget_state) = self
.temp_state .temp_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
if !self.canvas_data.temp_sensor_data.is_empty() { if !self.canvas_data.temp_sensor_data.is_empty() {
temp_widget_state.scroll_state.current_scroll_position = temp_widget_state.scroll_state.current_scroll_position =
@ -1478,8 +1489,7 @@ impl App {
BottomWidgetType::Disk => { BottomWidgetType::Disk => {
if let Some(disk_widget_state) = self if let Some(disk_widget_state) = self
.disk_state .disk_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
if !self.canvas_data.disk_data.is_empty() { if !self.canvas_data.disk_data.is_empty() {
disk_widget_state.scroll_state.current_scroll_position = disk_widget_state.scroll_state.current_scroll_position =
@ -1491,8 +1501,7 @@ impl App {
BottomWidgetType::CpuLegend => { BottomWidgetType::CpuLegend => {
if let Some(cpu_widget_state) = self if let Some(cpu_widget_state) = self
.cpu_state .cpu_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id - 1)
.get_mut(&(self.current_widget.widget_id - 1))
{ {
let cap = self.canvas_data.cpu_data.len(); let cap = self.canvas_data.cpu_data.len();
if cap > 0 { if cap > 0 {
@ -1564,8 +1573,7 @@ impl App {
fn change_process_position(&mut self, num_to_change_by: i64) { fn change_process_position(&mut self, num_to_change_by: i64) {
if let Some(proc_widget_state) = self if let Some(proc_widget_state) = self
.proc_state .proc_state
.widget_states .get_mut_widget_state(self.current_widget.widget_id)
.get_mut(&self.current_widget.widget_id)
{ {
let current_posn = proc_widget_state.scroll_state.current_scroll_position; let current_posn = proc_widget_state.scroll_state.current_scroll_position;

View File

@ -17,7 +17,7 @@ pub enum ProcessSorting {
CPU, CPU,
MEM, MEM,
PID, PID,
NAME, IDENTIFIER,
} }
impl Default for ProcessSorting { impl Default for ProcessSorting {
@ -32,6 +32,7 @@ pub struct ProcessHarvest {
pub cpu_usage_percent: f64, pub cpu_usage_percent: f64,
pub mem_usage_percent: f64, pub mem_usage_percent: f64,
pub name: String, pub name: String,
pub path: String,
pub read_bytes_per_sec: u64, pub read_bytes_per_sec: u64,
pub write_bytes_per_sec: u64, pub write_bytes_per_sec: u64,
pub total_read_bytes: u64, pub total_read_bytes: u64,
@ -46,6 +47,7 @@ pub struct PrevProcDetails {
pub total_write_bytes: u64, pub total_write_bytes: u64,
pub cpu_time: f64, pub cpu_time: f64,
pub proc_stat_path: PathBuf, pub proc_stat_path: PathBuf,
pub proc_exe_path: PathBuf,
pub proc_io_path: PathBuf, pub proc_io_path: PathBuf,
} }
@ -54,6 +56,7 @@ impl PrevProcDetails {
let pid_string = pid.to_string(); let pid_string = pid.to_string();
PrevProcDetails { PrevProcDetails {
proc_io_path: PathBuf::from(format!("/proc/{}/io", pid_string)), proc_io_path: PathBuf::from(format!("/proc/{}/io", pid_string)),
proc_exe_path: PathBuf::from(format!("/proc/{}/exe", pid_string)),
proc_stat_path: PathBuf::from(format!("/proc/{}/stat", pid_string)), proc_stat_path: PathBuf::from(format!("/proc/{}/stat", pid_string)),
..PrevProcDetails::default() ..PrevProcDetails::default()
} }
@ -174,7 +177,9 @@ fn get_linux_cpu_usage(
// Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556 // Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556
let after_proc_val = get_process_cpu_stats(&proc_stats); let after_proc_val = get_process_cpu_stats(&proc_stats);
if use_current_cpu_total { if cpu_usage == 0.0 {
Ok((0_f64, after_proc_val))
} else if use_current_cpu_total {
Ok(( Ok((
(after_proc_val - before_proc_val) / cpu_usage * 100_f64, (after_proc_val - before_proc_val) / cpu_usage * 100_f64,
after_proc_val, after_proc_val,
@ -194,17 +199,18 @@ fn convert_ps<S: core::hash::BuildHasher>(
new_pid_stats: &mut HashMap<u32, PrevProcDetails, S>, use_current_cpu_total: bool, new_pid_stats: &mut HashMap<u32, PrevProcDetails, S>, use_current_cpu_total: bool,
time_difference_in_secs: u64, time_difference_in_secs: u64,
) -> std::io::Result<ProcessHarvest> { ) -> std::io::Result<ProcessHarvest> {
let pid = (&process[..11]) let pid = (&process[..10])
.trim() .trim()
.to_string() .to_string()
.parse::<u32>() .parse::<u32>()
.unwrap_or(0); .unwrap_or(0);
let name = (&process[11..61]).trim().to_string(); let name = (&process[11..111]).trim().to_string();
let mem_usage_percent = (&process[62..]) let mem_usage_percent = (&process[112..116])
.trim() .trim()
.to_string() .to_string()
.parse::<f64>() .parse::<f64>()
.unwrap_or(0_f64); .unwrap_or(0_f64);
let path = (&process[117..]).trim().to_string();
let mut new_pid_stat = if let Some(prev_proc_stats) = prev_pid_stats.remove(&pid) { let mut new_pid_stat = if let Some(prev_proc_stats) = prev_pid_stats.remove(&pid) {
prev_proc_stats prev_proc_stats
@ -265,9 +271,11 @@ fn convert_ps<S: core::hash::BuildHasher>(
new_pid_stats.insert(pid, new_pid_stat); new_pid_stats.insert(pid, new_pid_stat);
// TODO: Is there a way to re-use these stats so I don't have to do so many syscalls?
Ok(ProcessHarvest { Ok(ProcessHarvest {
pid, pid,
name, name,
path,
mem_usage_percent, mem_usage_percent,
cpu_usage_percent, cpu_usage_percent,
total_read_bytes, total_read_bytes,
@ -286,7 +294,7 @@ pub fn linux_get_processes_list(
time_difference_in_secs: u64, time_difference_in_secs: u64,
) -> crate::utils::error::Result<Vec<ProcessHarvest>> { ) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
let ps_result = Command::new("ps") let ps_result = Command::new("ps")
.args(&["-axo", "pid:10,comm:50,%mem:5", "--noheader"]) .args(&["-axo", "pid:10,comm:100,%mem:5,args:100", "--noheader"])
.output()?; .output()?;
let ps_stdout = String::from_utf8_lossy(&ps_result.stdout); let ps_stdout = String::from_utf8_lossy(&ps_result.stdout);
let split_string = ps_stdout.split('\n'); let split_string = ps_stdout.split('\n');
@ -356,13 +364,21 @@ pub fn windows_macos_get_processes_list(
} else { } else {
process_val.name().to_string() process_val.name().to_string()
}; };
let path = {
let path = process_val.cmd().join(" ");
if path.is_empty() {
name.to_string()
} else {
path
}
};
let pcu = if cfg!(target_os = "windows") { let pcu = if cfg!(target_os = "windows") || num_cpus == 0.0 {
process_val.cpu_usage() as f64 process_val.cpu_usage() as f64
} else { } else {
process_val.cpu_usage() as f64 / num_cpus process_val.cpu_usage() as f64 / num_cpus
}; };
let process_cpu_usage = if use_current_cpu_total { let process_cpu_usage = if use_current_cpu_total && cpu_usage > 0.0 {
pcu / cpu_usage pcu / cpu_usage
} else { } else {
pcu pcu
@ -373,6 +389,7 @@ pub fn windows_macos_get_processes_list(
process_vector.push(ProcessHarvest { process_vector.push(ProcessHarvest {
pid: process_val.pid() as u32, pid: process_val.pid() as u32,
name, name,
path,
mem_usage_percent: if mem_total_kb > 0 { mem_usage_percent: if mem_total_kb > 0 {
process_val.memory() as f64 * 100.0 / mem_total_kb as f64 process_val.memory() as f64 * 100.0 / mem_total_kb as f64
} else { } else {

View File

@ -147,6 +147,9 @@ pub struct ProcWidgetState {
pub scroll_state: AppScrollWidgetState, pub scroll_state: AppScrollWidgetState,
pub process_sorting_type: processes::ProcessSorting, pub process_sorting_type: processes::ProcessSorting,
pub process_sorting_reverse: bool, pub process_sorting_reverse: bool,
pub is_using_full_path: bool,
pub current_column_index: usize,
pub num_columns: usize,
} }
impl ProcWidgetState { impl ProcWidgetState {
@ -171,6 +174,9 @@ impl ProcWidgetState {
scroll_state: AppScrollWidgetState::default(), scroll_state: AppScrollWidgetState::default(),
process_sorting_type: processes::ProcessSorting::CPU, process_sorting_type: processes::ProcessSorting::CPU,
process_sorting_reverse: true, process_sorting_reverse: true,
is_using_full_path: false,
current_column_index: 0,
num_columns: 1,
} }
} }
@ -263,6 +269,14 @@ impl ProcState {
force_update_all: false, force_update_all: false,
} }
} }
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut ProcWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&ProcWidgetState> {
self.widget_states.get(&widget_id)
}
} }
pub struct NetWidgetState { pub struct NetWidgetState {
@ -291,6 +305,14 @@ impl NetState {
widget_states, widget_states,
} }
} }
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut NetWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&NetWidgetState> {
self.widget_states.get(&widget_id)
}
} }
pub struct CpuWidgetState { pub struct CpuWidgetState {
@ -325,6 +347,14 @@ impl CpuState {
widget_states, widget_states,
} }
} }
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut CpuWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&CpuWidgetState> {
self.widget_states.get(&widget_id)
}
} }
pub struct MemWidgetState { pub struct MemWidgetState {
@ -353,6 +383,14 @@ impl MemState {
widget_states, widget_states,
} }
} }
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut MemWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&MemWidgetState> {
self.widget_states.get(&widget_id)
}
} }
pub struct TempWidgetState { pub struct TempWidgetState {
@ -375,6 +413,14 @@ impl TempState {
pub fn init(widget_states: HashMap<u64, TempWidgetState>) -> Self { pub fn init(widget_states: HashMap<u64, TempWidgetState>) -> Self {
TempState { widget_states } TempState { widget_states }
} }
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut TempWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&TempWidgetState> {
self.widget_states.get(&widget_id)
}
} }
pub struct DiskWidgetState { pub struct DiskWidgetState {
@ -397,6 +443,14 @@ impl DiskState {
pub fn init(widget_states: HashMap<u64, DiskWidgetState>) -> Self { pub fn init(widget_states: HashMap<u64, DiskWidgetState>) -> Self {
DiskState { widget_states } DiskState { widget_states }
} }
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut DiskWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&DiskWidgetState> {
self.widget_states.get(&widget_id)
}
} }
pub struct BasicTableWidgetState { pub struct BasicTableWidgetState {
// Since this is intended (currently) to only be used for ONE widget, that's // Since this is intended (currently) to only be used for ONE widget, that's
@ -420,6 +474,14 @@ impl BatteryState {
pub fn init(widget_states: HashMap<u64, BatteryWidgetState>) -> Self { pub fn init(widget_states: HashMap<u64, BatteryWidgetState>) -> Self {
BatteryState { widget_states } BatteryState { widget_states }
} }
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut BatteryWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&BatteryWidgetState> {
self.widget_states.get(&widget_id)
}
} }
#[derive(Default)] #[derive(Default)]

View File

@ -40,8 +40,6 @@ pub struct DisplayableData {
pub temp_sensor_data: Vec<Vec<String>>, pub temp_sensor_data: Vec<Vec<String>>,
// Not the final value // Not the final value
pub process_data: Vec<ConvertedProcessData>, pub process_data: Vec<ConvertedProcessData>,
// Not the final value
pub grouped_process_data: Vec<ConvertedProcessData>,
// What's actually displayed // What's actually displayed
pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>,
pub mem_label: String, pub mem_label: String,

View File

@ -28,20 +28,19 @@ impl KillDialog for Painter {
if app_state.is_grouped(app_state.current_widget.widget_id) { if app_state.is_grouped(app_state.current_widget.widget_id) {
if to_kill_processes.1.len() != 1 { if to_kill_processes.1.len() != 1 {
Text::raw(format!( Text::raw(format!(
"\nKill {} processes with the name {}?", "\nKill {} processes with the name \"{}\"?",
to_kill_processes.1.len(), to_kill_processes.1.len(),
to_kill_processes.0 to_kill_processes.0
)) ))
} else { } else {
Text::raw(format!( Text::raw(format!(
"\nKill {} process with the name {}?", "\nKill 1 process with the name \"{}\"?",
to_kill_processes.1.len(),
to_kill_processes.0 to_kill_processes.0
)) ))
} }
} else { } else {
Text::raw(format!( Text::raw(format!(
"\nKill process {} with PID {}?", "\nKill process \"{}\" with PID {}?",
to_kill_processes.0, first_pid to_kill_processes.0, first_pid
)) ))
}, },

View File

@ -31,7 +31,7 @@ impl HelpDialog for Painter {
// small terminal sizes... oh joy. // small terminal sizes... oh joy.
let mut overflow_buffer = 0; let mut overflow_buffer = 0;
let paragraph_width = draw_loc.width - 2; let paragraph_width = std::cmp::max(draw_loc.width.saturating_sub(2), 1);
let mut prev_section_len = 0; let mut prev_section_len = 0;
constants::HELP_TEXT constants::HELP_TEXT

View File

@ -133,13 +133,17 @@ impl ProcessTableWidget for Painter {
}); });
use app::data_harvester::processes::ProcessSorting; use app::data_harvester::processes::ProcessSorting;
let mut pid_or_name = if proc_widget_state.is_grouped { let mut pid_or_count = if proc_widget_state.is_grouped {
"Count" "Count"
} else { } else {
"PID(p)" "PID(p)"
} }
.to_string(); .to_string();
let mut name = "Name(n)".to_string(); let mut identifier = if proc_widget_state.is_using_full_path {
"Command(n)".to_string()
} else {
"Name(n)".to_string()
};
let mut cpu = "CPU%(c)".to_string(); let mut cpu = "CPU%(c)".to_string();
let mut mem = "Mem%(m)".to_string(); let mut mem = "Mem%(m)".to_string();
let rps = "R/s".to_string(); let rps = "R/s".to_string();
@ -157,14 +161,15 @@ impl ProcessTableWidget for Painter {
match proc_widget_state.process_sorting_type { match proc_widget_state.process_sorting_type {
ProcessSorting::CPU => cpu += &direction_val, ProcessSorting::CPU => cpu += &direction_val,
ProcessSorting::MEM => mem += &direction_val, ProcessSorting::MEM => mem += &direction_val,
ProcessSorting::PID => pid_or_name += &direction_val, ProcessSorting::PID => pid_or_count += &direction_val,
ProcessSorting::NAME => name += &direction_val, ProcessSorting::IDENTIFIER => identifier += &direction_val,
}; };
// TODO: Gonna have to figure out how to do left/right GUI notation.
let process_headers = if proc_widget_state.is_grouped { let process_headers = if proc_widget_state.is_grouped {
vec![ vec![
pid_or_name, pid_or_count,
name, identifier,
cpu, cpu,
mem, mem,
rps, rps,
@ -174,8 +179,8 @@ impl ProcessTableWidget for Painter {
] ]
} else { } else {
vec![ vec![
pid_or_name, pid_or_count,
name, identifier,
cpu, cpu,
mem, mem,
rps, rps,
@ -185,6 +190,7 @@ impl ProcessTableWidget for Painter {
process_state, process_state,
] ]
}; };
proc_widget_state.num_columns = process_headers.len();
let process_headers_lens: Vec<usize> = process_headers let process_headers_lens: Vec<usize> = process_headers
.iter() .iter()
.map(|entry| entry.len()) .map(|entry| entry.len())
@ -192,8 +198,16 @@ impl ProcessTableWidget for Painter {
// Calculate widths // Calculate widths
let width = f64::from(draw_loc.width); let width = f64::from(draw_loc.width);
// TODO: This is a ugly work-around for now.
let width_ratios = if proc_widget_state.is_grouped { let width_ratios = if proc_widget_state.is_grouped {
vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.15, 0.15] if proc_widget_state.is_using_full_path {
vec![0.1, 0.7, 0.05, 0.05, 0.025, 0.025, 0.025, 0.025]
} else {
vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.15, 0.15]
}
} else if proc_widget_state.is_using_full_path {
vec![0.1, 0.7, 0.05, 0.05, 0.02, 0.02, 0.02, 0.02, 0.02]
} else { } else {
vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
}; };

View File

@ -79,7 +79,7 @@ pub const CPU_HELP_TEXT: [&str; 2] = [
"Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart", "Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart",
]; ];
pub const PROCESS_HELP_TEXT: [&str; 8] = [ pub const PROCESS_HELP_TEXT: [&str; 9] = [
"3 - Process widget\n", "3 - Process widget\n",
"dd Kill the selected process\n", "dd Kill the selected process\n",
"c Sort by CPU usage, press again to reverse sorting order\n", "c Sort by CPU usage, press again to reverse sorting order\n",
@ -87,7 +87,8 @@ pub const PROCESS_HELP_TEXT: [&str; 8] = [
"p Sort by PID name, press again to reverse sorting order\n", "p Sort by PID name, press again to reverse sorting order\n",
"n Sort by process name, press again to reverse sorting order\n", "n Sort by process name, press again to reverse sorting order\n",
"Tab Group/un-group processes with the same name\n", "Tab Group/un-group processes with the same name\n",
"Ctrl-f, / Open process search widget", "Ctrl-f, / Open process search widget\n",
"P Toggle between showing the full path or just the process name",
]; ];
pub const SEARCH_HELP_TEXT: [&str; 43] = [ pub const SEARCH_HELP_TEXT: [&str; 43] = [

View File

@ -359,99 +359,122 @@ pub fn convert_network_data_points(
} }
} }
pub enum ProcessGroupingType {
Grouped,
Ungrouped,
}
pub enum ProcessNamingType {
Name,
Path,
}
pub fn convert_process_data( pub fn convert_process_data(
current_data: &data_farmer::DataCollection, current_data: &data_farmer::DataCollection, grouping_type: ProcessGroupingType,
) -> (Vec<ConvertedProcessData>, Vec<ConvertedProcessData>) { name_type: ProcessNamingType,
let mut single_list = Vec::new(); ) -> Vec<ConvertedProcessData> {
match grouping_type {
ProcessGroupingType::Ungrouped => current_data
.process_harvest
.iter()
.map(|process| {
let converted_rps = get_exact_byte_values(process.read_bytes_per_sec, false);
let converted_wps = get_exact_byte_values(process.write_bytes_per_sec, false);
let converted_total_read = get_exact_byte_values(process.total_read_bytes, false);
let converted_total_write = get_exact_byte_values(process.total_write_bytes, false);
// cpu, mem, pids let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1);
let mut grouped_hashmap: HashMap<String, SingleProcessData> = std::collections::HashMap::new(); let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1);
let total_read =
format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1);
let total_write = format!(
"{:.*}{}",
0, converted_total_write.0, converted_total_write.1
);
// Go through every single process in the list... and build a hashmap + single list ConvertedProcessData {
for process in &(current_data).process_harvest { pid: process.pid,
let entry = grouped_hashmap name: match name_type {
.entry(process.name.clone()) ProcessNamingType::Name => process.name.to_string(),
.or_insert(SingleProcessData { ProcessNamingType::Path => process.path.to_string(),
pid: process.pid, },
..SingleProcessData::default() cpu_usage: process.cpu_usage_percent,
mem_usage: process.mem_usage_percent,
group_pids: vec![process.pid],
read_per_sec,
write_per_sec,
total_read,
total_write,
rps_f64: process.read_bytes_per_sec as f64,
wps_f64: process.write_bytes_per_sec as f64,
tr_f64: process.total_read_bytes as f64,
tw_f64: process.total_write_bytes as f64,
process_states: process.process_state.to_owned(),
}
})
.collect::<Vec<_>>(),
ProcessGroupingType::Grouped => {
let mut grouped_hashmap: HashMap<String, SingleProcessData> =
std::collections::HashMap::new();
current_data.process_harvest.iter().for_each(|process| {
let entry = grouped_hashmap
.entry(match name_type {
ProcessNamingType::Name => process.name.to_string(),
ProcessNamingType::Path => process.path.to_string(),
})
.or_insert(SingleProcessData {
pid: process.pid,
..SingleProcessData::default()
});
(*entry).cpu_usage += process.cpu_usage_percent;
(*entry).mem_usage += process.mem_usage_percent;
(*entry).group_pids.push(process.pid);
(*entry).read_per_sec += process.read_bytes_per_sec;
(*entry).write_per_sec += process.write_bytes_per_sec;
(*entry).total_read += process.total_read_bytes;
(*entry).total_write += process.total_write_bytes;
}); });
(*entry).cpu_usage += process.cpu_usage_percent; grouped_hashmap
(*entry).mem_usage += process.mem_usage_percent; .iter()
(*entry).group_pids.push(process.pid); .map(|(identifier, process_details)| {
(*entry).read_per_sec += process.read_bytes_per_sec; let p = process_details.clone();
(*entry).write_per_sec += process.write_bytes_per_sec; let converted_rps = get_exact_byte_values(p.read_per_sec, false);
(*entry).total_read += process.total_read_bytes; let converted_wps = get_exact_byte_values(p.write_per_sec, false);
(*entry).total_write += process.total_write_bytes; let converted_total_read = get_exact_byte_values(p.total_read, false);
let converted_total_write = get_exact_byte_values(p.total_write, false);
let converted_rps = get_exact_byte_values(process.read_bytes_per_sec, false); let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1);
let converted_wps = get_exact_byte_values(process.write_bytes_per_sec, false); let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1);
let converted_total_read = get_exact_byte_values(process.total_read_bytes, false); let total_read =
let converted_total_write = get_exact_byte_values(process.total_write_bytes, false); format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1);
let total_write = format!(
"{:.*}{}",
0, converted_total_write.0, converted_total_write.1
);
let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1); ConvertedProcessData {
let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1); pid: p.pid,
let total_read = format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1); name: identifier.to_string(),
let total_write = format!( cpu_usage: p.cpu_usage,
"{:.*}{}", mem_usage: p.mem_usage,
0, converted_total_write.0, converted_total_write.1 group_pids: p.group_pids,
); read_per_sec,
write_per_sec,
single_list.push(ConvertedProcessData { total_read,
pid: process.pid, total_write,
name: process.name.to_string(), rps_f64: p.read_per_sec as f64,
cpu_usage: process.cpu_usage_percent, wps_f64: p.write_per_sec as f64,
mem_usage: process.mem_usage_percent, tr_f64: p.total_read as f64,
group_pids: vec![process.pid], tw_f64: p.total_write as f64,
read_per_sec, process_states: p.process_state,
write_per_sec, }
total_read, })
total_write, .collect::<Vec<_>>()
rps_f64: process.read_bytes_per_sec as f64, }
wps_f64: process.write_bytes_per_sec as f64,
tr_f64: process.total_read_bytes as f64,
tw_f64: process.total_write_bytes as f64,
process_states: process.process_state.to_owned(),
});
} }
let grouped_list: Vec<ConvertedProcessData> = grouped_hashmap
.iter()
.map(|(name, process_details)| {
let p = process_details.clone();
let converted_rps = get_exact_byte_values(p.read_per_sec, false);
let converted_wps = get_exact_byte_values(p.write_per_sec, false);
let converted_total_read = get_exact_byte_values(p.total_read, false);
let converted_total_write = get_exact_byte_values(p.total_write, false);
let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1);
let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1);
let total_read = format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1);
let total_write = format!(
"{:.*}{}",
0, converted_total_write.0, converted_total_write.1
);
ConvertedProcessData {
pid: p.pid,
name: name.to_string(),
cpu_usage: p.cpu_usage,
mem_usage: p.mem_usage,
group_pids: p.group_pids,
read_per_sec,
write_per_sec,
total_read,
total_write,
rps_f64: p.read_per_sec as f64,
wps_f64: p.write_per_sec as f64,
tr_f64: p.total_read as f64,
tw_f64: p.total_write as f64,
process_states: p.process_state,
}
})
.collect::<Vec<_>>();
(single_list, grouped_list)
} }
pub fn convert_battery_harvest( pub fn convert_battery_harvest(

View File

@ -225,9 +225,6 @@ fn main() -> error::Result<()> {
// Processes // Processes
if app.used_widgets.use_proc { if app.used_widgets.use_proc {
let (single, grouped) = convert_process_data(&app.data_collection);
app.canvas_data.process_data = single;
app.canvas_data.grouped_process_data = grouped;
update_all_process_lists(&mut app); update_all_process_lists(&mut app);
} }
@ -582,44 +579,45 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
.is_invalid_or_blank_search(), .is_invalid_or_blank_search(),
None => false, None => false,
}; };
let is_grouped = app.is_grouped(widget_id);
if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
app.canvas_data.process_data = convert_process_data(
&app.data_collection,
if is_grouped {
ProcessGroupingType::Grouped
} else {
ProcessGroupingType::Ungrouped
},
if proc_widget_state.is_using_full_path {
ProcessNamingType::Path
} else {
ProcessNamingType::Name
},
);
}
let process_filter = app.get_process_filter(widget_id); let process_filter = app.get_process_filter(widget_id);
let filtered_process_data: Vec<ConvertedProcessData> = if app.is_grouped(widget_id) { let filtered_process_data: Vec<ConvertedProcessData> = app
app.canvas_data .canvas_data
.grouped_process_data .process_data
.iter() .iter()
.filter(|process| { .filter(|process| {
if is_invalid_or_blank { if !is_invalid_or_blank {
true if let Some(process_filter) = process_filter {
} else if let Some(process_filter) = process_filter { process_filter.check(&process)
process_filter.check(process)
} else { } else {
true true
} }
}) } else {
.cloned() true
.collect::<Vec<_>>() }
} else { })
app.canvas_data .cloned()
.process_data .collect::<Vec<_>>();
.iter()
.filter(|process| {
if !is_invalid_or_blank {
if let Some(process_filter) = process_filter {
process_filter.check(&process)
} else {
true
}
} else {
true
}
})
.cloned()
.collect::<Vec<_>>()
};
// Quick fix for tab updating the table headers // Quick fix for tab updating the table headers
if let Some(proc_widget_state) = app.proc_state.widget_states.get_mut(&widget_id) { if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
if let data_harvester::processes::ProcessSorting::PID = if let data_harvester::processes::ProcessSorting::PID =
proc_widget_state.process_sorting_type proc_widget_state.process_sorting_type
{ {
@ -672,7 +670,7 @@ fn sort_process_data(
) )
}); });
} }
ProcessSorting::NAME => { ProcessSorting::IDENTIFIER => {
// Don't repeat if false... // Don't repeat if false...
if proc_widget_state.process_sorting_reverse { if proc_widget_state.process_sorting_reverse {
to_sort_vec.sort_by(|a, b| { to_sort_vec.sort_by(|a, b| {

View File

@ -357,8 +357,6 @@ fn get_temperature(
/// Yes, this function gets whether to show average CPU (true) or not (false) /// Yes, this function gets whether to show average CPU (true) or not (false)
fn get_show_average_cpu(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { fn get_show_average_cpu(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
// FIXME: Update the demo config file and default config files! Need to remove
// old options and change to hide_avg_cpu option.
if matches.is_present("HIDE_AVG_CPU") { if matches.is_present("HIDE_AVG_CPU") {
return false; return false;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {