summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2021-02-28 17:40:55 -0500
committerGitHub <noreply@github.com>2021-02-28 17:40:55 -0500
commit53d8bdae3280854c428b4bf7de4bf765bcd4f6e4 (patch)
treefa949fe0d5c8ae12305e1cc85b27b5d4895f3817
parentc406d956994dc0e7d1ef734dcc402ad49ae642f6 (diff)
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.
-rw-r--r--.vscode/settings.json2
-rw-r--r--CHANGELOG.md18
-rw-r--r--Cargo.toml5
-rw-r--r--README.md3
-rw-r--r--src/app.rs56
-rw-r--r--src/app/data_harvester/processes.rs67
-rw-r--r--src/app/query.rs27
-rw-r--r--src/app/states.rs56
-rw-r--r--src/canvas/widgets/process_table.rs16
-rw-r--r--src/constants.rs3
-rw-r--r--src/data_conversion.rs42
-rw-r--r--src/lib.rs12
-rw-r--r--src/utils/error.rs6
13 files changed, 265 insertions, 48 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index a04896e8..5d567693 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -67,11 +67,13 @@
"cvars",
"czvf",
"denylist",
+ "doctest",
"dont",
"eselect",
"fedoracentos",
"fpath",
"fract",
+ "getpwuid",
"gnueabihf",
"gotop",
"gotop's",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3176e72b..0cc22531 100644
--- a/CHANGELOG.md
+++ b/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
diff --git a/Cargo.toml b/Cargo.toml
index 7e944d23..b9351b53 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
diff --git a/README.md b/README.md
index a899ef27..f0a929ff 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/src/app.rs b/src/app.rs
index 8be7394d..3c0fc9d5 100644
--- a/src/app.rs
+++ b/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;
diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs
index c9233337..56bf09d4 100644
--- a/src/app/data_harvester/processes.rs
+++ b/src/app/data_harvester/processes.rs
@@ -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,
});
}
}
diff --git a/src/app/query.rs b/src/app/query.rs
index 79f61e73..5a827aec 100644
--- a/src/app/query.rs
+++ b/src/app/query.rs
@@ -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 {
diff --git a/src/app/states.rs b/src/app/states.rs
index c2853337..39dd0222 100644
--- a/src/app/states.rs
+++ b/src/app/states.rs
@@ -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
diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs
index 316e71ae..2abab933 100644
--- a/src/canvas/widgets/process_table.rs
+++ b/src/canvas/widgets/process_table.rs
@@ -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))
{
diff --git a/src/constants.rs b/src/constants.rs
index 38c5ef6a..fa6d4b6c 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -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:",
diff --git a/src/data_conversion.rs b/src/data_conversion.rs
index b3614cf1..c3665cda 100644
--- a/src/data_conversion.rs
+++ b/src/data_conversion.rs
@@ -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<_>>()
diff --git a/src/lib.rs b/src/lib.rs
index f5e73a27..e3be6823 100644
--- a/src/lib.rs
+++ b/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| {
diff --git a/src/utils/error.rs b/src/utils/error.rs
index f0a965b0..1fc75bd7 100644
--- a/src/utils/error.rs
+++ b/src/utils/error.rs
@@ -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: