mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-10 14:44:18 +00: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
13 changed files with 265 additions and 48 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -67,11 +67,13 @@
|
||||||
"cvars",
|
"cvars",
|
||||||
"czvf",
|
"czvf",
|
||||||
"denylist",
|
"denylist",
|
||||||
|
"doctest",
|
||||||
"dont",
|
"dont",
|
||||||
"eselect",
|
"eselect",
|
||||||
"fedoracentos",
|
"fedoracentos",
|
||||||
"fpath",
|
"fpath",
|
||||||
"fract",
|
"fract",
|
||||||
|
"getpwuid",
|
||||||
"gnueabihf",
|
"gnueabihf",
|
||||||
"gotop",
|
"gotop",
|
||||||
"gotop's",
|
"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
|
## 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
|
## 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.
|
- [#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
|
## [0.5.7] - 2021-01-30
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
|
@ -17,6 +17,11 @@ name = "btm"
|
||||||
path = "src/bin/main.rs"
|
path = "src/bin/main.rs"
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
test = false
|
||||||
|
doctest = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 0
|
debug = 0
|
||||||
lto = true
|
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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `state` | `state=running` | Matches by state; supports regex |
|
||||||
|
|
||||||
#### Supported comparison operators
|
#### Supported comparison operators
|
||||||
|
@ -464,7 +465,7 @@ As yet _another_ process/system visualization and management application, bottom
|
||||||
|
|
||||||
- Display temperatures from sensors
|
- 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)
|
- 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))]
|
#[builder(default = false, setter(skip))]
|
||||||
pub did_config_fail_to_save: bool,
|
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 cpu_state: CpuState,
|
||||||
pub mem_state: MemState,
|
pub mem_state: MemState,
|
||||||
pub net_state: NetState,
|
pub net_state: NetState,
|
||||||
|
@ -310,23 +314,35 @@ impl App {
|
||||||
|
|
||||||
// Forcefully switch off column if we were on it...
|
// Forcefully switch off column if we were on it...
|
||||||
if (proc_widget_state.is_grouped
|
if (proc_widget_state.is_grouped
|
||||||
&& proc_widget_state.process_sorting_type
|
&& (proc_widget_state.process_sorting_type
|
||||||
== data_harvester::processes::ProcessSorting::Pid)
|
== 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.is_grouped
|
||||||
&& proc_widget_state.process_sorting_type
|
&& proc_widget_state.process_sorting_type
|
||||||
== data_harvester::processes::ProcessSorting::Count)
|
== processes::ProcessSorting::Count)
|
||||||
{
|
{
|
||||||
proc_widget_state.process_sorting_type =
|
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.is_process_sort_descending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
proc_widget_state
|
proc_widget_state.columns.set_to_sorted_index_from_type(
|
||||||
.columns
|
&proc_widget_state.process_sorting_type,
|
||||||
.column_mapping
|
);
|
||||||
.get_mut(&processes::ProcessSorting::State)
|
|
||||||
.unwrap()
|
proc_widget_state.columns.try_set(
|
||||||
.enabled = !(proc_widget_state.is_grouped);
|
&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
|
proc_widget_state
|
||||||
.columns
|
.columns
|
||||||
|
@ -657,6 +673,26 @@ impl App {
|
||||||
proc_widget_state.is_tree_mode = !proc_widget_state.is_tree_mode;
|
proc_widget_state.is_tree_mode = !proc_widget_state.is_tree_mode;
|
||||||
|
|
||||||
if 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.
|
// We enabled... set PID sort type to ascending.
|
||||||
proc_widget_state.process_sorting_type = processes::ProcessSorting::Pid;
|
proc_widget_state.process_sorting_type = processes::ProcessSorting::Pid;
|
||||||
proc_widget_state.is_process_sort_descending = false;
|
proc_widget_state.is_process_sort_descending = false;
|
||||||
|
|
|
@ -2,8 +2,11 @@ use crate::Pid;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use sysinfo::ProcessStatus;
|
use sysinfo::ProcessStatus;
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
use crate::utils::error;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use crate::utils::error::{self, BottomError};
|
use crate::utils::error::BottomError;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use fnv::{FnvHashMap, FnvHashSet};
|
use fnv::{FnvHashMap, FnvHashSet};
|
||||||
|
@ -29,28 +32,29 @@ pub enum ProcessSorting {
|
||||||
TotalRead,
|
TotalRead,
|
||||||
TotalWrite,
|
TotalWrite,
|
||||||
State,
|
State,
|
||||||
|
User,
|
||||||
Count,
|
Count,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ProcessSorting {
|
impl std::fmt::Display for ProcessSorting {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
use ProcessSorting::*;
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
match &self {
|
match &self {
|
||||||
CpuPercent => "CPU%",
|
ProcessSorting::CpuPercent => "CPU%",
|
||||||
MemPercent => "Mem%",
|
ProcessSorting::MemPercent => "Mem%",
|
||||||
Mem => "Mem",
|
ProcessSorting::Mem => "Mem",
|
||||||
ReadPerSecond => "R/s",
|
ProcessSorting::ReadPerSecond => "R/s",
|
||||||
WritePerSecond => "W/s",
|
ProcessSorting::WritePerSecond => "W/s",
|
||||||
TotalRead => "T.Read",
|
ProcessSorting::TotalRead => "T.Read",
|
||||||
TotalWrite => "T.Write",
|
ProcessSorting::TotalWrite => "T.Write",
|
||||||
State => "State",
|
ProcessSorting::State => "State",
|
||||||
ProcessName => "Name",
|
ProcessSorting::ProcessName => "Name",
|
||||||
Command => "Command",
|
ProcessSorting::Command => "Command",
|
||||||
Pid => "PID",
|
ProcessSorting::Pid => "PID",
|
||||||
Count => "Count",
|
ProcessSorting::Count => "Count",
|
||||||
|
ProcessSorting::User => "User",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -81,9 +85,13 @@ pub struct ProcessHarvest {
|
||||||
pub process_state_char: char,
|
pub process_state_char: char,
|
||||||
|
|
||||||
/// This is the *effective* user ID.
|
/// This is the *effective* user ID.
|
||||||
pub uid: Option<u32>,
|
#[cfg(target_family = "unix")]
|
||||||
// pub real_uid: Option<u32>, // TODO: Add real user ID
|
pub uid: Option<libc::uid_t>,
|
||||||
pub gid: Option<u32>,
|
|
||||||
|
// TODO: Add real user ID
|
||||||
|
// pub real_uid: Option<u32>,
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
pub gid: Option<libc::gid_t>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[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")]
|
#[cfg(target_os = "linux")]
|
||||||
fn cpu_usage_calculation(
|
fn cpu_usage_calculation(
|
||||||
prev_idle: &mut f64, prev_non_idle: &mut f64,
|
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,
|
total_write_bytes: disk_usage.total_written_bytes,
|
||||||
process_state: process_val.status().to_string().to_string(),
|
process_state: process_val.status().to_string().to_string(),
|
||||||
process_state_char: convert_process_status_to_char(process_val.status()),
|
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).
|
/// - 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.
|
/// - 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.
|
/// - 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.
|
/// - Read/s: Use prefix `r`. Can compare.
|
||||||
/// - Write/s: Use prefix `w`. Can compare.
|
/// - Write/s: Use prefix `w`. Can compare.
|
||||||
/// - Total read: Use prefix `read`. 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> {
|
fn process_prefix(query: &mut VecDeque<String>, inside_quotation: bool) -> Result<Prefix> {
|
||||||
if let Some(queue_top) = query.pop_front() {
|
if let Some(queue_top) = query.pop_front() {
|
||||||
// debug!("Prefix QT: {:?}", queue_top);
|
|
||||||
|
|
||||||
if inside_quotation {
|
if inside_quotation {
|
||||||
if queue_top == "\"" {
|
if queue_top == "\"" {
|
||||||
// This means we hit something like "". Return an empty prefix, and to deal with
|
// 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,
|
compare_prefix: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
PrefixType::Pid | PrefixType::State => {
|
PrefixType::Pid | PrefixType::State | PrefixType::User => {
|
||||||
// We have to check if someone put an "="...
|
// We have to check if someone put an "="...
|
||||||
if content == "=" {
|
if content == "=" {
|
||||||
// Check next string if possible
|
// Check next string if possible
|
||||||
if let Some(queue_next) = query.pop_front() {
|
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 {
|
return Ok(Prefix {
|
||||||
or: None,
|
or: None,
|
||||||
regex_prefix: Some((
|
regex_prefix: Some((
|
||||||
|
@ -580,6 +588,7 @@ pub enum PrefixType {
|
||||||
TWrite,
|
TWrite,
|
||||||
Name,
|
Name,
|
||||||
State,
|
State,
|
||||||
|
User,
|
||||||
__Nonexhaustive,
|
__Nonexhaustive,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -602,6 +611,7 @@ impl std::str::FromStr for PrefixType {
|
||||||
"twrite" | "t.write" => Ok(TWrite),
|
"twrite" | "t.write" => Ok(TWrite),
|
||||||
"pid" => Ok(Pid),
|
"pid" => Ok(Pid),
|
||||||
"state" => Ok(State),
|
"state" => Ok(State),
|
||||||
|
"user" => Ok(User),
|
||||||
_ => Ok(Name),
|
_ => Ok(Name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -628,7 +638,7 @@ impl Prefix {
|
||||||
} else if let Some((prefix_type, StringQuery::Value(regex_string))) = &mut self.regex_prefix
|
} else if let Some((prefix_type, StringQuery::Value(regex_string))) = &mut self.regex_prefix
|
||||||
{
|
{
|
||||||
match prefix_type {
|
match prefix_type {
|
||||||
PrefixType::Pid | PrefixType::Name | PrefixType::State => {
|
PrefixType::Pid | PrefixType::Name | PrefixType::State | PrefixType::User => {
|
||||||
let escaped_regex: String;
|
let escaped_regex: String;
|
||||||
let final_regex_string = &format!(
|
let final_regex_string = &format!(
|
||||||
"{}{}{}{}",
|
"{}{}{}{}",
|
||||||
|
@ -681,6 +691,13 @@ impl Prefix {
|
||||||
}),
|
}),
|
||||||
PrefixType::Pid => r.is_match(process.pid.to_string().as_str()),
|
PrefixType::Pid => r.is_match(process.pid.to_string().as_str()),
|
||||||
PrefixType::State => r.is_match(process.process_state.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,
|
_ => true,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -174,6 +174,9 @@ impl ProcessSearchState {
|
||||||
pub struct ColumnInfo {
|
pub struct ColumnInfo {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub shortcut: Option<&'static str>,
|
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 {
|
pub struct ProcColumn {
|
||||||
|
@ -205,6 +208,7 @@ impl Default for ProcColumn {
|
||||||
WritePerSecond,
|
WritePerSecond,
|
||||||
TotalRead,
|
TotalRead,
|
||||||
TotalWrite,
|
TotalWrite,
|
||||||
|
User,
|
||||||
State,
|
State,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -219,6 +223,8 @@ impl Default for ProcColumn {
|
||||||
ColumnInfo {
|
ColumnInfo {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
shortcut: Some("c"),
|
shortcut: Some("c"),
|
||||||
|
// hard_width: None,
|
||||||
|
// max_soft_width: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -228,6 +234,8 @@ impl Default for ProcColumn {
|
||||||
ColumnInfo {
|
ColumnInfo {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
shortcut: Some("m"),
|
shortcut: Some("m"),
|
||||||
|
// hard_width: None,
|
||||||
|
// max_soft_width: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -237,6 +245,8 @@ impl Default for ProcColumn {
|
||||||
ColumnInfo {
|
ColumnInfo {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
shortcut: Some("m"),
|
shortcut: Some("m"),
|
||||||
|
// hard_width: None,
|
||||||
|
// max_soft_width: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -246,6 +256,8 @@ impl Default for ProcColumn {
|
||||||
ColumnInfo {
|
ColumnInfo {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
shortcut: Some("n"),
|
shortcut: Some("n"),
|
||||||
|
// hard_width: None,
|
||||||
|
// max_soft_width: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -255,6 +267,8 @@ impl Default for ProcColumn {
|
||||||
ColumnInfo {
|
ColumnInfo {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
shortcut: Some("n"),
|
shortcut: Some("n"),
|
||||||
|
// hard_width: None,
|
||||||
|
// max_soft_width: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -264,6 +278,8 @@ impl Default for ProcColumn {
|
||||||
ColumnInfo {
|
ColumnInfo {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
shortcut: Some("p"),
|
shortcut: Some("p"),
|
||||||
|
// hard_width: None,
|
||||||
|
// max_soft_width: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -273,6 +289,17 @@ impl Default for ProcColumn {
|
||||||
ColumnInfo {
|
ColumnInfo {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
shortcut: None,
|
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 {
|
ColumnInfo {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
shortcut: None,
|
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 {
|
pub fn is_enabled(&self, column: &ProcessSorting) -> bool {
|
||||||
if let Some(mapping) = self.column_mapping.get(column) {
|
if let Some(mapping) = self.column_mapping.get(column) {
|
||||||
mapping.enabled
|
mapping.enabled
|
||||||
|
|
|
@ -30,6 +30,8 @@ static PROCESS_HEADERS_HARD_WIDTH_NO_GROUP: Lazy<Vec<Option<u16>>> = Lazy::new(|
|
||||||
Some(8),
|
Some(8),
|
||||||
Some(7),
|
Some(7),
|
||||||
Some(8),
|
Some(8),
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
None,
|
||||||
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>>> =
|
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]);
|
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>>> =
|
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]);
|
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,
|
None,
|
||||||
None,
|
None,
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
Some(0.05),
|
||||||
Some(0.2),
|
Some(0.2),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -76,6 +78,8 @@ static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE: Lazy<Vec<Option<f64>>> = La
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
Some(0.05),
|
||||||
Some(0.2),
|
Some(0.2),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -89,6 +93,8 @@ static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE: Lazy<Vec<Option<f64>>> = La
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
Some(0.05),
|
||||||
Some(0.2),
|
Some(0.2),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -344,6 +350,7 @@ impl ProcessTableWidget for Painter {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calculate widths
|
// 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 {
|
let hard_widths = if proc_widget_state.is_grouped {
|
||||||
&*PROCESS_HEADERS_HARD_WIDTH_GROUPED
|
&*PROCESS_HEADERS_HARD_WIDTH_GROUPED
|
||||||
} else {
|
} else {
|
||||||
|
@ -394,10 +401,10 @@ impl ProcessTableWidget for Painter {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let soft_widths_max = if proc_widget_state.is_grouped {
|
let soft_widths_max = if proc_widget_state.is_grouped {
|
||||||
|
// Note grouped trees are not a thing.
|
||||||
|
|
||||||
if proc_widget_state.is_using_command {
|
if proc_widget_state.is_using_command {
|
||||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND
|
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND
|
||||||
} else if proc_widget_state.is_tree_mode {
|
|
||||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_TREE
|
|
||||||
} else {
|
} else {
|
||||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_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) =
|
if let Some(proc_widget_state) =
|
||||||
app_state.proc_state.widget_states.get_mut(&(widget_id - 1))
|
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",
|
"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",
|
"4 - Process search widget",
|
||||||
"Tab Toggle between searching for PID and name",
|
"Tab Toggle between searching for PID and name",
|
||||||
"Esc Close the search widget (retains the filter)",
|
"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",
|
"write, w/s ex: write <= 1 tb",
|
||||||
"tread, t.read ex: tread = 1",
|
"tread, t.read ex: tread = 1",
|
||||||
"twrite, t.write ex: twrite = 1",
|
"twrite, t.write ex: twrite = 1",
|
||||||
|
"user ex: user = root",
|
||||||
"state ex: state = running",
|
"state ex: state = running",
|
||||||
"",
|
"",
|
||||||
"Comparison operators:",
|
"Comparison operators:",
|
||||||
|
|
|
@ -62,6 +62,7 @@ pub struct ConvertedProcessData {
|
||||||
pub tw_f64: f64,
|
pub tw_f64: f64,
|
||||||
pub process_state: String,
|
pub process_state: String,
|
||||||
pub process_char: char,
|
pub process_char: char,
|
||||||
|
pub user: Option<String>,
|
||||||
|
|
||||||
/// Prefix printed before the process when displayed.
|
/// Prefix printed before the process when displayed.
|
||||||
pub process_description_prefix: Option<String>,
|
pub process_description_prefix: Option<String>,
|
||||||
|
@ -482,6 +483,7 @@ pub enum ProcessNamingType {
|
||||||
pub fn convert_process_data(
|
pub fn convert_process_data(
|
||||||
current_data: &data_farmer::DataCollection,
|
current_data: &data_farmer::DataCollection,
|
||||||
existing_converted_process_data: &mut HashMap<Pid, ConvertedProcessData>,
|
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
|
// TODO [THREAD]: Thread highlighting and hiding support
|
||||||
// For macOS see https://github.com/hishamhm/htop/pull/848/files
|
// 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
|
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) {
|
if let Some(process_entry) = existing_converted_process_data.get_mut(&process.pid) {
|
||||||
complete_pid_set.remove(&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_char = process.process_state_char;
|
||||||
process_entry.process_description_prefix = None;
|
process_entry.process_description_prefix = None;
|
||||||
process_entry.is_disabled_entry = false;
|
process_entry.is_disabled_entry = false;
|
||||||
|
process_entry.user = user;
|
||||||
} else {
|
} else {
|
||||||
// ...I hate that I can't combine if let and an if statement in one line...
|
// ...I hate that I can't combine if let and an if statement in one line...
|
||||||
*process_entry = ConvertedProcessData {
|
*process_entry = ConvertedProcessData {
|
||||||
|
@ -553,6 +571,7 @@ pub fn convert_process_data(
|
||||||
process_description_prefix: None,
|
process_description_prefix: None,
|
||||||
is_disabled_entry: false,
|
is_disabled_entry: false,
|
||||||
is_collapsed_entry: false,
|
is_collapsed_entry: false,
|
||||||
|
user,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -582,6 +601,7 @@ pub fn convert_process_data(
|
||||||
process_description_prefix: None,
|
process_description_prefix: None,
|
||||||
is_disabled_entry: false,
|
is_disabled_entry: false,
|
||||||
is_collapsed_entry: false,
|
is_collapsed_entry: false,
|
||||||
|
user,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -827,8 +847,18 @@ pub fn tree_process_data(
|
||||||
is_sort_descending,
|
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 => {
|
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.write_per_sec.clone(), None),
|
||||||
(process.total_read.clone(), None),
|
(process.total_read.clone(), None),
|
||||||
(process.total_write.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(),
|
process.process_state.clone(),
|
||||||
Some(process.process_char.to_string()),
|
Some(process.process_char.to_string()),
|
||||||
|
@ -1083,6 +1122,7 @@ pub fn group_process_data(
|
||||||
process_char: char::default(),
|
process_char: char::default(),
|
||||||
is_disabled_entry: false,
|
is_disabled_entry: false,
|
||||||
is_collapsed_entry: false,
|
is_collapsed_entry: false,
|
||||||
|
user: None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.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(
|
convert_process_data(
|
||||||
&app.data_collection,
|
&app.data_collection,
|
||||||
&mut app.canvas_data.single_process_data,
|
&mut app.canvas_data.single_process_data,
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
&mut app.user_table,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let process_filter = app.get_process_filter(widget_id);
|
let process_filter = app.get_process_filter(widget_id);
|
||||||
|
@ -550,6 +552,16 @@ fn sort_process_data(
|
||||||
to_sort_vec.reverse();
|
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 => {
|
ProcessSorting::Count => {
|
||||||
if proc_widget_state.is_grouped {
|
if proc_widget_state.is_grouped {
|
||||||
to_sort_vec.sort_by(|a, b| {
|
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 {
|
impl From<regex::Error> for BottomError {
|
||||||
fn from(err: regex::Error) -> Self {
|
fn from(err: regex::Error) -> Self {
|
||||||
// We only really want the last part of it... so we'll do it the ugly way:
|
// We only really want the last part of it... so we'll do it the ugly way:
|
||||||
|
|
Loading…
Reference in a new issue