mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-04-08 17:05:59 +02:00
feature: User info in proc widget for Unix-based systems (#425)
Adds users into the process widget (for Unix-based systems). This shows only in non-grouped modes, similar to state. Search is also supported. In addition, a quick fix to prevent users from being in grouped mode when they tried to enter tree mode while grouped.
This commit is contained in:
parent
c406d95699
commit
53d8bdae32
.vscode
CHANGELOG.mdCargo.tomlREADME.mdsrc
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -67,11 +67,13 @@
|
||||
"cvars",
|
||||
"czvf",
|
||||
"denylist",
|
||||
"doctest",
|
||||
"dont",
|
||||
"eselect",
|
||||
"fedoracentos",
|
||||
"fpath",
|
||||
"fract",
|
||||
"getpwuid",
|
||||
"gnueabihf",
|
||||
"gotop",
|
||||
"gotop's",
|
||||
|
18
CHANGELOG.md
18
CHANGELOG.md
@ -9,19 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Features
|
||||
|
||||
- [#263](https://github.com/ClementTsang/bottom/pull/263): Adds the option for fine-grained kill signals on Unix-like systems.
|
||||
- [#263](https://github.com/ClementTsang/bottom/pull/263): Added the option for fine-grained kill signals on Unix-like systems.
|
||||
|
||||
- [#333](https://github.com/ClementTsang/bottom/pull/333): Adds an "out of" indicator that can be enabled using `--show_table_scroll_position` (and its corresponding config option) to help keep track of scrolled position.
|
||||
- [#333](https://github.com/ClementTsang/bottom/pull/333): Added an "out of" indicator that can be enabled using `--show_table_scroll_position` (and its corresponding config option) to help keep track of scrolled position.
|
||||
|
||||
- [#379](https://github.com/ClementTsang/bottom/pull/379): Adds `--process_command` flag and corresponding config option to default to showing a process' command.
|
||||
- [#379](https://github.com/ClementTsang/bottom/pull/379): Added `--process_command` flag and corresponding config option to default to showing a process' command.
|
||||
|
||||
- [#381](https://github.com/ClementTsang/bottom/pull/381): Adds a filter in the config file for network interfaces.
|
||||
- [#381](https://github.com/ClementTsang/bottom/pull/381): Added a filter in the config file for network interfaces.
|
||||
|
||||
- [#406](https://github.com/ClementTsang/bottom/pull/406): Adds the Nord colour scheme, as well as a light variant.
|
||||
- [#406](https://github.com/ClementTsang/bottom/pull/406): Added the Nord colour scheme, as well as a light variant.
|
||||
|
||||
- [#409](https://github.com/ClementTsang/bottom/pull/409): Adds `Ctrl-w` and `Ctrl-h` shortcuts in search, to delete a word and delete a character respectively.
|
||||
- [#409](https://github.com/ClementTsang/bottom/pull/409): Added `Ctrl-w` and `Ctrl-h` shortcuts in search, to delete a word and delete a character respectively.
|
||||
|
||||
- [#413](https://github.com/ClementTsang/bottom/pull/413): Adds mouse support for sorting process columns.
|
||||
- [#413](https://github.com/ClementTsang/bottom/pull/413): Added mouse support for sorting process columns.
|
||||
|
||||
- [#425](https://github.com/ClementTsang/bottom/pull/425): Added user into the process widget for Unix-based systems.
|
||||
|
||||
## Changes
|
||||
|
||||
@ -43,6 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- [#423](https://github.com/ClementTsang/bottom/pull/423): Fixes disk encryption causing the disk widget to fail or not properly map I/O statistics.
|
||||
|
||||
- [#425](https://github.com/ClementTsang/bottom/pull/425): Fixed a bug allowing grouped mode in tree mode if already in grouped mode.
|
||||
|
||||
## [0.5.7] - 2021-01-30
|
||||
|
||||
## Bug Fixes
|
||||
|
@ -17,6 +17,11 @@ name = "btm"
|
||||
path = "src/bin/main.rs"
|
||||
doc = false
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
doctest = false
|
||||
doc = false
|
||||
|
||||
[profile.release]
|
||||
debug = 0
|
||||
lto = true
|
||||
|
@ -387,6 +387,7 @@ Use `btm --help` for more information.
|
||||
| `write`, `w/s` | `write >= 1 kb` | Matches the write/s column in terms of bytes; supports comparison operators |
|
||||
| `tread`, `t.read` | `tread <= 1024 gb` | Matches he total read column in terms of bytes; supports comparison operators |
|
||||
| `twrite`, `t.write` | `twrite > 1024 tb` | Matches the total write column in terms of bytes; supports comparison operators |
|
||||
| `user` | `user=root` | Matches by user; supports regex |
|
||||
| `state` | `state=running` | Matches by state; supports regex |
|
||||
|
||||
#### Supported comparison operators
|
||||
@ -464,7 +465,7 @@ As yet _another_ process/system visualization and management application, bottom
|
||||
|
||||
- Display temperatures from sensors
|
||||
|
||||
- Display information regarding processes, like CPU, memory, I/O usage, and process state
|
||||
- Display information regarding processes, like CPU, memory, I/O usage, user, and process state
|
||||
|
||||
- Process management (well, if process killing is all you need)
|
||||
|
||||
|
56
src/app.rs
56
src/app.rs
@ -121,6 +121,10 @@ pub struct App {
|
||||
#[builder(default = false, setter(skip))]
|
||||
pub did_config_fail_to_save: bool,
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[builder(default, setter(skip))]
|
||||
pub user_table: processes::UserTable,
|
||||
|
||||
pub cpu_state: CpuState,
|
||||
pub mem_state: MemState,
|
||||
pub net_state: NetState,
|
||||
@ -310,23 +314,35 @@ impl App {
|
||||
|
||||
// Forcefully switch off column if we were on it...
|
||||
if (proc_widget_state.is_grouped
|
||||
&& proc_widget_state.process_sorting_type
|
||||
== data_harvester::processes::ProcessSorting::Pid)
|
||||
&& (proc_widget_state.process_sorting_type
|
||||
== processes::ProcessSorting::Pid
|
||||
|| proc_widget_state.process_sorting_type
|
||||
== processes::ProcessSorting::User
|
||||
|| proc_widget_state.process_sorting_type
|
||||
== processes::ProcessSorting::State))
|
||||
|| (!proc_widget_state.is_grouped
|
||||
&& proc_widget_state.process_sorting_type
|
||||
== data_harvester::processes::ProcessSorting::Count)
|
||||
== processes::ProcessSorting::Count)
|
||||
{
|
||||
proc_widget_state.process_sorting_type =
|
||||
data_harvester::processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group
|
||||
processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group
|
||||
proc_widget_state.is_process_sort_descending = true;
|
||||
}
|
||||
|
||||
proc_widget_state
|
||||
.columns
|
||||
.column_mapping
|
||||
.get_mut(&processes::ProcessSorting::State)
|
||||
.unwrap()
|
||||
.enabled = !(proc_widget_state.is_grouped);
|
||||
proc_widget_state.columns.set_to_sorted_index_from_type(
|
||||
&proc_widget_state.process_sorting_type,
|
||||
);
|
||||
|
||||
proc_widget_state.columns.try_set(
|
||||
&processes::ProcessSorting::State,
|
||||
!(proc_widget_state.is_grouped),
|
||||
);
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
proc_widget_state.columns.try_set(
|
||||
&processes::ProcessSorting::User,
|
||||
!(proc_widget_state.is_grouped),
|
||||
);
|
||||
|
||||
proc_widget_state
|
||||
.columns
|
||||
@ -657,6 +673,26 @@ impl App {
|
||||
proc_widget_state.is_tree_mode = !proc_widget_state.is_tree_mode;
|
||||
|
||||
if proc_widget_state.is_tree_mode {
|
||||
// Disable grouping if so!
|
||||
proc_widget_state.is_grouped = false;
|
||||
|
||||
proc_widget_state
|
||||
.columns
|
||||
.try_enable(&processes::ProcessSorting::State);
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
proc_widget_state
|
||||
.columns
|
||||
.try_enable(&processes::ProcessSorting::User);
|
||||
|
||||
proc_widget_state
|
||||
.columns
|
||||
.try_disable(&processes::ProcessSorting::Count);
|
||||
|
||||
proc_widget_state
|
||||
.columns
|
||||
.try_enable(&processes::ProcessSorting::Pid);
|
||||
|
||||
// We enabled... set PID sort type to ascending.
|
||||
proc_widget_state.process_sorting_type = processes::ProcessSorting::Pid;
|
||||
proc_widget_state.is_process_sort_descending = false;
|
||||
|
@ -2,8 +2,11 @@ use crate::Pid;
|
||||
use std::path::PathBuf;
|
||||
use sysinfo::ProcessStatus;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use crate::utils::error;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::utils::error::{self, BottomError};
|
||||
use crate::utils::error::BottomError;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
@ -29,28 +32,29 @@ pub enum ProcessSorting {
|
||||
TotalRead,
|
||||
TotalWrite,
|
||||
State,
|
||||
User,
|
||||
Count,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ProcessSorting {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use ProcessSorting::*;
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match &self {
|
||||
CpuPercent => "CPU%",
|
||||
MemPercent => "Mem%",
|
||||
Mem => "Mem",
|
||||
ReadPerSecond => "R/s",
|
||||
WritePerSecond => "W/s",
|
||||
TotalRead => "T.Read",
|
||||
TotalWrite => "T.Write",
|
||||
State => "State",
|
||||
ProcessName => "Name",
|
||||
Command => "Command",
|
||||
Pid => "PID",
|
||||
Count => "Count",
|
||||
ProcessSorting::CpuPercent => "CPU%",
|
||||
ProcessSorting::MemPercent => "Mem%",
|
||||
ProcessSorting::Mem => "Mem",
|
||||
ProcessSorting::ReadPerSecond => "R/s",
|
||||
ProcessSorting::WritePerSecond => "W/s",
|
||||
ProcessSorting::TotalRead => "T.Read",
|
||||
ProcessSorting::TotalWrite => "T.Write",
|
||||
ProcessSorting::State => "State",
|
||||
ProcessSorting::ProcessName => "Name",
|
||||
ProcessSorting::Command => "Command",
|
||||
ProcessSorting::Pid => "PID",
|
||||
ProcessSorting::Count => "Count",
|
||||
ProcessSorting::User => "User",
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -81,9 +85,13 @@ pub struct ProcessHarvest {
|
||||
pub process_state_char: char,
|
||||
|
||||
/// This is the *effective* user ID.
|
||||
pub uid: Option<u32>,
|
||||
// pub real_uid: Option<u32>, // TODO: Add real user ID
|
||||
pub gid: Option<u32>,
|
||||
#[cfg(target_family = "unix")]
|
||||
pub uid: Option<libc::uid_t>,
|
||||
|
||||
// TODO: Add real user ID
|
||||
// pub real_uid: Option<u32>,
|
||||
#[cfg(target_family = "unix")]
|
||||
pub gid: Option<libc::gid_t>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
@ -114,6 +122,29 @@ impl PrevProcDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct UserTable {
|
||||
pub uid_user_mapping: std::collections::HashMap<libc::uid_t, String>,
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
impl UserTable {
|
||||
pub fn get_uid_to_username_mapping(&mut self, uid: libc::uid_t) -> error::Result<String> {
|
||||
if let Some(user) = self.uid_user_mapping.get(&uid) {
|
||||
Ok(user.clone())
|
||||
} else {
|
||||
let passwd = unsafe { libc::getpwuid(uid) };
|
||||
let username = unsafe { std::ffi::CStr::from_ptr((*passwd).pw_name) }
|
||||
.to_str()?
|
||||
.to_string();
|
||||
self.uid_user_mapping.insert(uid, username.clone());
|
||||
|
||||
Ok(username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn cpu_usage_calculation(
|
||||
prev_idle: &mut f64, prev_non_idle: &mut f64,
|
||||
@ -591,8 +622,6 @@ pub fn get_process_data(
|
||||
total_write_bytes: disk_usage.total_written_bytes,
|
||||
process_state: process_val.status().to_string().to_string(),
|
||||
process_state_char: convert_process_status_to_char(process_val.status()),
|
||||
uid: None,
|
||||
gid: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ pub trait ProcessQuery {
|
||||
/// - PIDs: Use prefix `pid`, can use regex or match word (case is irrelevant).
|
||||
/// - CPU: Use prefix `cpu`, cannot use r/m/c (regex, match word, case). Can compare.
|
||||
/// - MEM: Use prefix `mem`, cannot use r/m/c. Can compare.
|
||||
/// - STATE: Use prefix `state`, TODO when we update how state looks in 0.5 probably.
|
||||
/// - STATE: Use prefix `state`, can use regex, match word, or case.
|
||||
/// - USER: Use prefix `user`, can use regex, match word, or case.
|
||||
/// - Read/s: Use prefix `r`. Can compare.
|
||||
/// - Write/s: Use prefix `w`. Can compare.
|
||||
/// - Total read: Use prefix `read`. Can compare.
|
||||
@ -128,8 +129,6 @@ impl ProcessQuery for ProcWidgetState {
|
||||
|
||||
fn process_prefix(query: &mut VecDeque<String>, inside_quotation: bool) -> Result<Prefix> {
|
||||
if let Some(queue_top) = query.pop_front() {
|
||||
// debug!("Prefix QT: {:?}", queue_top);
|
||||
|
||||
if inside_quotation {
|
||||
if queue_top == "\"" {
|
||||
// This means we hit something like "". Return an empty prefix, and to deal with
|
||||
@ -264,11 +263,20 @@ impl ProcessQuery for ProcWidgetState {
|
||||
compare_prefix: None,
|
||||
})
|
||||
}
|
||||
PrefixType::Pid | PrefixType::State => {
|
||||
PrefixType::Pid | PrefixType::State | PrefixType::User => {
|
||||
// We have to check if someone put an "="...
|
||||
if content == "=" {
|
||||
// Check next string if possible
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
// TODO: Need to consider the following cases:
|
||||
// - (test)
|
||||
// - (test
|
||||
// - test)
|
||||
// These are split into 2 to 3 different strings due to parentheses being
|
||||
// delimiters in our query system.
|
||||
//
|
||||
// Do we want these to be valid? They should, as a string, right?
|
||||
|
||||
return Ok(Prefix {
|
||||
or: None,
|
||||
regex_prefix: Some((
|
||||
@ -580,6 +588,7 @@ pub enum PrefixType {
|
||||
TWrite,
|
||||
Name,
|
||||
State,
|
||||
User,
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
@ -602,6 +611,7 @@ impl std::str::FromStr for PrefixType {
|
||||
"twrite" | "t.write" => Ok(TWrite),
|
||||
"pid" => Ok(Pid),
|
||||
"state" => Ok(State),
|
||||
"user" => Ok(User),
|
||||
_ => Ok(Name),
|
||||
}
|
||||
}
|
||||
@ -628,7 +638,7 @@ impl Prefix {
|
||||
} else if let Some((prefix_type, StringQuery::Value(regex_string))) = &mut self.regex_prefix
|
||||
{
|
||||
match prefix_type {
|
||||
PrefixType::Pid | PrefixType::Name | PrefixType::State => {
|
||||
PrefixType::Pid | PrefixType::Name | PrefixType::State | PrefixType::User => {
|
||||
let escaped_regex: String;
|
||||
let final_regex_string = &format!(
|
||||
"{}{}{}{}",
|
||||
@ -681,6 +691,13 @@ impl Prefix {
|
||||
}),
|
||||
PrefixType::Pid => r.is_match(process.pid.to_string().as_str()),
|
||||
PrefixType::State => r.is_match(process.process_state.as_str()),
|
||||
PrefixType::User => {
|
||||
if let Some(user) = &process.user {
|
||||
r.is_match(user.as_str())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
} else {
|
||||
|
@ -174,6 +174,9 @@ impl ProcessSearchState {
|
||||
pub struct ColumnInfo {
|
||||
pub enabled: bool,
|
||||
pub shortcut: Option<&'static str>,
|
||||
// FIXME: Move column width logic here!
|
||||
// pub hard_width: Option<u16>,
|
||||
// pub max_soft_width: Option<f64>,
|
||||
}
|
||||
|
||||
pub struct ProcColumn {
|
||||
@ -205,6 +208,7 @@ impl Default for ProcColumn {
|
||||
WritePerSecond,
|
||||
TotalRead,
|
||||
TotalWrite,
|
||||
User,
|
||||
State,
|
||||
];
|
||||
|
||||
@ -219,6 +223,8 @@ impl Default for ProcColumn {
|
||||
ColumnInfo {
|
||||
enabled: true,
|
||||
shortcut: Some("c"),
|
||||
// hard_width: None,
|
||||
// max_soft_width: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -228,6 +234,8 @@ impl Default for ProcColumn {
|
||||
ColumnInfo {
|
||||
enabled: true,
|
||||
shortcut: Some("m"),
|
||||
// hard_width: None,
|
||||
// max_soft_width: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -237,6 +245,8 @@ impl Default for ProcColumn {
|
||||
ColumnInfo {
|
||||
enabled: false,
|
||||
shortcut: Some("m"),
|
||||
// hard_width: None,
|
||||
// max_soft_width: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -246,6 +256,8 @@ impl Default for ProcColumn {
|
||||
ColumnInfo {
|
||||
enabled: true,
|
||||
shortcut: Some("n"),
|
||||
// hard_width: None,
|
||||
// max_soft_width: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -255,6 +267,8 @@ impl Default for ProcColumn {
|
||||
ColumnInfo {
|
||||
enabled: false,
|
||||
shortcut: Some("n"),
|
||||
// hard_width: None,
|
||||
// max_soft_width: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -264,6 +278,8 @@ impl Default for ProcColumn {
|
||||
ColumnInfo {
|
||||
enabled: true,
|
||||
shortcut: Some("p"),
|
||||
// hard_width: None,
|
||||
// max_soft_width: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -273,6 +289,17 @@ impl Default for ProcColumn {
|
||||
ColumnInfo {
|
||||
enabled: false,
|
||||
shortcut: None,
|
||||
// hard_width: None,
|
||||
// max_soft_width: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
User => {
|
||||
column_mapping.insert(
|
||||
column,
|
||||
ColumnInfo {
|
||||
enabled: cfg!(target_family = "unix"),
|
||||
shortcut: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -282,6 +309,8 @@ impl Default for ProcColumn {
|
||||
ColumnInfo {
|
||||
enabled: true,
|
||||
shortcut: None,
|
||||
// hard_width: None,
|
||||
// max_soft_width: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -316,6 +345,33 @@ impl ProcColumn {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_set(&mut self, column: &ProcessSorting, setting: bool) -> Option<bool> {
|
||||
if let Some(mapping) = self.column_mapping.get_mut(column) {
|
||||
mapping.enabled = setting;
|
||||
Some(mapping.enabled)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_enable(&mut self, column: &ProcessSorting) -> Option<bool> {
|
||||
if let Some(mapping) = self.column_mapping.get_mut(column) {
|
||||
mapping.enabled = true;
|
||||
Some(mapping.enabled)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_disable(&mut self, column: &ProcessSorting) -> Option<bool> {
|
||||
if let Some(mapping) = self.column_mapping.get_mut(column) {
|
||||
mapping.enabled = false;
|
||||
Some(mapping.enabled)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self, column: &ProcessSorting) -> bool {
|
||||
if let Some(mapping) = self.column_mapping.get(column) {
|
||||
mapping.enabled
|
||||
|
@ -30,6 +30,8 @@ static PROCESS_HEADERS_HARD_WIDTH_NO_GROUP: Lazy<Vec<Option<u16>>> = Lazy::new(|
|
||||
Some(8),
|
||||
Some(7),
|
||||
Some(8),
|
||||
#[cfg(target_family = "unix")]
|
||||
None,
|
||||
None,
|
||||
]
|
||||
});
|
||||
@ -48,8 +50,6 @@ static PROCESS_HEADERS_HARD_WIDTH_GROUPED: Lazy<Vec<Option<u16>>> = Lazy::new(||
|
||||
|
||||
static PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND: Lazy<Vec<Option<f64>>> =
|
||||
Lazy::new(|| vec![None, Some(0.7), None, None, None, None, None, None]);
|
||||
static PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_TREE: Lazy<Vec<Option<f64>>> =
|
||||
Lazy::new(|| vec![None, Some(0.5), None, None, None, None, None, None]);
|
||||
static PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE: Lazy<Vec<Option<f64>>> =
|
||||
Lazy::new(|| vec![None, Some(0.3), None, None, None, None, None, None]);
|
||||
|
||||
@ -63,6 +63,8 @@ static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_COMMAND: Lazy<Vec<Option<f64>>> =
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(target_family = "unix")]
|
||||
Some(0.05),
|
||||
Some(0.2),
|
||||
]
|
||||
});
|
||||
@ -76,6 +78,8 @@ static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE: Lazy<Vec<Option<f64>>> = La
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(target_family = "unix")]
|
||||
Some(0.05),
|
||||
Some(0.2),
|
||||
]
|
||||
});
|
||||
@ -89,6 +93,8 @@ static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE: Lazy<Vec<Option<f64>>> = La
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(target_family = "unix")]
|
||||
Some(0.05),
|
||||
Some(0.2),
|
||||
]
|
||||
});
|
||||
@ -344,6 +350,7 @@ impl ProcessTableWidget for Painter {
|
||||
);
|
||||
|
||||
// Calculate widths
|
||||
// FIXME: See if we can move this into the recalculate block? I want to move column widths into the column widths
|
||||
let hard_widths = if proc_widget_state.is_grouped {
|
||||
&*PROCESS_HEADERS_HARD_WIDTH_GROUPED
|
||||
} else {
|
||||
@ -394,10 +401,10 @@ impl ProcessTableWidget for Painter {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let soft_widths_max = if proc_widget_state.is_grouped {
|
||||
// Note grouped trees are not a thing.
|
||||
|
||||
if proc_widget_state.is_using_command {
|
||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND
|
||||
} else if proc_widget_state.is_tree_mode {
|
||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_TREE
|
||||
} else {
|
||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE
|
||||
}
|
||||
@ -601,6 +608,7 @@ impl ProcessTableWidget for Painter {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make the cursor scroll back if there's space!
|
||||
if let Some(proc_widget_state) =
|
||||
app_state.proc_state.widget_states.get_mut(&(widget_id - 1))
|
||||
{
|
||||
|
@ -272,7 +272,7 @@ pub const PROCESS_HELP_TEXT: [&str; 15] = [
|
||||
"click on header Sorts the entries by that column, click again to invert the sort",
|
||||
];
|
||||
|
||||
pub const SEARCH_HELP_TEXT: [&str; 48] = [
|
||||
pub const SEARCH_HELP_TEXT: [&str; 49] = [
|
||||
"4 - Process search widget",
|
||||
"Tab Toggle between searching for PID and name",
|
||||
"Esc Close the search widget (retains the filter)",
|
||||
@ -299,6 +299,7 @@ pub const SEARCH_HELP_TEXT: [&str; 48] = [
|
||||
"write, w/s ex: write <= 1 tb",
|
||||
"tread, t.read ex: tread = 1",
|
||||
"twrite, t.write ex: twrite = 1",
|
||||
"user ex: user = root",
|
||||
"state ex: state = running",
|
||||
"",
|
||||
"Comparison operators:",
|
||||
|
@ -62,6 +62,7 @@ pub struct ConvertedProcessData {
|
||||
pub tw_f64: f64,
|
||||
pub process_state: String,
|
||||
pub process_char: char,
|
||||
pub user: Option<String>,
|
||||
|
||||
/// Prefix printed before the process when displayed.
|
||||
pub process_description_prefix: Option<String>,
|
||||
@ -482,6 +483,7 @@ pub enum ProcessNamingType {
|
||||
pub fn convert_process_data(
|
||||
current_data: &data_farmer::DataCollection,
|
||||
existing_converted_process_data: &mut HashMap<Pid, ConvertedProcessData>,
|
||||
#[cfg(target_family = "unix")] user_table: &mut data_harvester::processes::UserTable,
|
||||
) {
|
||||
// TODO [THREAD]: Thread highlighting and hiding support
|
||||
// For macOS see https://github.com/hishamhm/htop/pull/848/files
|
||||
@ -503,6 +505,21 @@ pub fn convert_process_data(
|
||||
0, converted_total_write.0, converted_total_write.1
|
||||
);
|
||||
|
||||
let user = {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
if let Some(uid) = process.uid {
|
||||
user_table.get_uid_to_username_mapping(uid).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(process_entry) = existing_converted_process_data.get_mut(&process.pid) {
|
||||
complete_pid_set.remove(&process.pid);
|
||||
|
||||
@ -527,6 +544,7 @@ pub fn convert_process_data(
|
||||
process_entry.process_char = process.process_state_char;
|
||||
process_entry.process_description_prefix = None;
|
||||
process_entry.is_disabled_entry = false;
|
||||
process_entry.user = user;
|
||||
} else {
|
||||
// ...I hate that I can't combine if let and an if statement in one line...
|
||||
*process_entry = ConvertedProcessData {
|
||||
@ -553,6 +571,7 @@ pub fn convert_process_data(
|
||||
process_description_prefix: None,
|
||||
is_disabled_entry: false,
|
||||
is_collapsed_entry: false,
|
||||
user,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@ -582,6 +601,7 @@ pub fn convert_process_data(
|
||||
process_description_prefix: None,
|
||||
is_disabled_entry: false,
|
||||
is_collapsed_entry: false,
|
||||
user,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -827,8 +847,18 @@ pub fn tree_process_data(
|
||||
is_sort_descending,
|
||||
)
|
||||
}),
|
||||
ProcessSorting::User => to_sort_vec.sort_by(|a, b| match (&a.1.user, &b.1.user) {
|
||||
(Some(user_a), Some(user_b)) => utils::gen_util::get_ordering(
|
||||
user_a.to_lowercase(),
|
||||
user_b.to_lowercase(),
|
||||
is_sort_descending,
|
||||
),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => std::cmp::Ordering::Less,
|
||||
}),
|
||||
ProcessSorting::Count => {
|
||||
// Should never occur in this case.
|
||||
// Should never occur in this case, tree mode explicitly disables grouping.
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -990,6 +1020,15 @@ pub fn stringify_process_data(
|
||||
(process.write_per_sec.clone(), None),
|
||||
(process.total_read.clone(), None),
|
||||
(process.total_write.clone(), None),
|
||||
#[cfg(target_family = "unix")]
|
||||
(
|
||||
if let Some(user) = &process.user {
|
||||
user.clone()
|
||||
} else {
|
||||
"N/A".to_string()
|
||||
},
|
||||
None,
|
||||
),
|
||||
(
|
||||
process.process_state.clone(),
|
||||
Some(process.process_char.to_string()),
|
||||
@ -1083,6 +1122,7 @@ pub fn group_process_data(
|
||||
process_char: char::default(),
|
||||
is_disabled_entry: false,
|
||||
is_collapsed_entry: false,
|
||||
user: None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
12
src/lib.rs
12
src/lib.rs
@ -369,6 +369,8 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
|
||||
convert_process_data(
|
||||
&app.data_collection,
|
||||
&mut app.canvas_data.single_process_data,
|
||||
#[cfg(target_family = "unix")]
|
||||
&mut app.user_table,
|
||||
);
|
||||
}
|
||||
let process_filter = app.get_process_filter(widget_id);
|
||||
@ -550,6 +552,16 @@ fn sort_process_data(
|
||||
to_sort_vec.reverse();
|
||||
}
|
||||
}
|
||||
ProcessSorting::User => to_sort_vec.sort_by(|a, b| match (&a.user, &b.user) {
|
||||
(Some(user_a), Some(user_b)) => utils::gen_util::get_ordering(
|
||||
user_a.to_lowercase(),
|
||||
user_b.to_lowercase(),
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => std::cmp::Ordering::Less,
|
||||
}),
|
||||
ProcessSorting::Count => {
|
||||
if proc_widget_state.is_grouped {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
|
@ -86,6 +86,12 @@ impl From<std::str::Utf8Error> for BottomError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for BottomError {
|
||||
fn from(err: std::string::FromUtf8Error) -> Self {
|
||||
BottomError::ConversionError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<regex::Error> for BottomError {
|
||||
fn from(err: regex::Error) -> Self {
|
||||
// We only really want the last part of it... so we'll do it the ugly way:
|
||||
|
Loading…
x
Reference in New Issue
Block a user