mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-27 14:40:43 +00:00
feature: support default selection of average CPU graph (#1353)
* feature: support default selection of average CPU graph * test
This commit is contained in:
parent
300f66700e
commit
b6f92c2f3d
11 changed files with 152 additions and 34 deletions
|
@ -3,15 +3,3 @@ rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
[target.i686-pc-windows-msvc]
|
[target.i686-pc-windows-msvc]
|
||||||
rustflags = ["-C", "target-feature=+crt-static"]
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
# [target.arm-unknown-linux-gnueabihf]
|
|
||||||
# linker = "arm-linux-gnueabihf-gcc"
|
|
||||||
|
|
||||||
# [target.armv7-unknown-linux-gnueabihf]
|
|
||||||
# linker = "arm-linux-gnueabihf-gcc"
|
|
||||||
|
|
||||||
# [target.aarch64-unknown-linux-gnu]
|
|
||||||
# linker = "aarch64-linux-gnu-gcc"
|
|
||||||
|
|
||||||
# [target.aarch64-unknown-linux-musl]
|
|
||||||
# linker = "aarch64-linux-musl-gcc"
|
|
11
docs/content/configuration/config-file/cpu.md
Normal file
11
docs/content/configuration/config-file/cpu.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# CPU
|
||||||
|
|
||||||
|
## Default CPU Graph Selection
|
||||||
|
|
||||||
|
You can configure which CPU graph is shown by default when starting up bottom by setting `cpu.default`.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[cpu]
|
||||||
|
# One of "all" (default), "average"/"avg"
|
||||||
|
default = "average"
|
||||||
|
```
|
|
@ -85,6 +85,10 @@
|
||||||
#[processes]
|
#[processes]
|
||||||
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"]
|
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"]
|
||||||
|
|
||||||
|
# [cpu]
|
||||||
|
# One of "all" (default), "average"/"avg"
|
||||||
|
# default = "average"
|
||||||
|
|
||||||
# These are all the components that support custom theming. Note that colour support
|
# These are all the components that support custom theming. Note that colour support
|
||||||
# will depend on terminal support.
|
# will depend on terminal support.
|
||||||
|
|
||||||
|
|
22
src/app.rs
22
src/app.rs
|
@ -49,7 +49,7 @@ pub struct AppConfigFields {
|
||||||
pub temperature_type: temperature::TemperatureType,
|
pub temperature_type: temperature::TemperatureType,
|
||||||
pub use_dot: bool,
|
pub use_dot: bool,
|
||||||
pub left_legend: bool,
|
pub left_legend: bool,
|
||||||
pub show_average_cpu: bool,
|
pub show_average_cpu: bool, // TODO: Unify this in CPU options
|
||||||
pub use_current_cpu_total: bool,
|
pub use_current_cpu_total: bool,
|
||||||
pub unnormalized_cpu: bool,
|
pub unnormalized_cpu: bool,
|
||||||
pub use_basic_mode: bool,
|
pub use_basic_mode: bool,
|
||||||
|
@ -1910,7 +1910,7 @@ impl App {
|
||||||
.proc_state
|
.proc_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
.get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
{
|
||||||
proc_widget_state.table.set_first();
|
proc_widget_state.table.to_first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomWidgetType::ProcSort => {
|
BottomWidgetType::ProcSort => {
|
||||||
|
@ -1919,7 +1919,7 @@ impl App {
|
||||||
.proc_state
|
.proc_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id - 2)
|
.get_mut_widget_state(self.current_widget.widget_id - 2)
|
||||||
{
|
{
|
||||||
proc_widget_state.sort_table.set_first();
|
proc_widget_state.sort_table.to_first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomWidgetType::Temp => {
|
BottomWidgetType::Temp => {
|
||||||
|
@ -1928,7 +1928,7 @@ impl App {
|
||||||
.temp_state
|
.temp_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
.get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
{
|
||||||
temp_widget_state.table.set_first();
|
temp_widget_state.table.to_first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomWidgetType::Disk => {
|
BottomWidgetType::Disk => {
|
||||||
|
@ -1937,7 +1937,7 @@ impl App {
|
||||||
.disk_state
|
.disk_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
.get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
{
|
||||||
disk_widget_state.table.set_first();
|
disk_widget_state.table.to_first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomWidgetType::CpuLegend => {
|
BottomWidgetType::CpuLegend => {
|
||||||
|
@ -1946,7 +1946,7 @@ impl App {
|
||||||
.cpu_state
|
.cpu_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id - 1)
|
.get_mut_widget_state(self.current_widget.widget_id - 1)
|
||||||
{
|
{
|
||||||
cpu_widget_state.table.set_first();
|
cpu_widget_state.table.to_first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1969,7 +1969,7 @@ impl App {
|
||||||
.proc_state
|
.proc_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
.get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
{
|
||||||
proc_widget_state.table.set_last();
|
proc_widget_state.table.to_last();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomWidgetType::ProcSort => {
|
BottomWidgetType::ProcSort => {
|
||||||
|
@ -1978,7 +1978,7 @@ impl App {
|
||||||
.proc_state
|
.proc_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id - 2)
|
.get_mut_widget_state(self.current_widget.widget_id - 2)
|
||||||
{
|
{
|
||||||
proc_widget_state.sort_table.set_last();
|
proc_widget_state.sort_table.to_last();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomWidgetType::Temp => {
|
BottomWidgetType::Temp => {
|
||||||
|
@ -1987,7 +1987,7 @@ impl App {
|
||||||
.temp_state
|
.temp_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
.get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
{
|
||||||
temp_widget_state.table.set_last();
|
temp_widget_state.table.to_last();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomWidgetType::Disk => {
|
BottomWidgetType::Disk => {
|
||||||
|
@ -1997,7 +1997,7 @@ impl App {
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
.get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
{
|
||||||
if !self.converted_data.disk_data.is_empty() {
|
if !self.converted_data.disk_data.is_empty() {
|
||||||
disk_widget_state.table.set_last();
|
disk_widget_state.table.to_last();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2007,7 +2007,7 @@ impl App {
|
||||||
.cpu_state
|
.cpu_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id - 1)
|
.get_mut_widget_state(self.current_widget.widget_id - 1)
|
||||||
{
|
{
|
||||||
cpu_widget_state.table.set_last();
|
cpu_widget_state.table.to_last();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
@ -40,6 +40,7 @@ pub struct DataTable<DataType, Header, S = Unsortable, C = Column<Header>> {
|
||||||
data: Vec<DataType>,
|
data: Vec<DataType>,
|
||||||
sort_type: S,
|
sort_type: S,
|
||||||
first_draw: bool,
|
first_draw: bool,
|
||||||
|
first_index: Option<usize>,
|
||||||
_pd: PhantomData<(DataType, S, Header)>,
|
_pd: PhantomData<(DataType, S, Header)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ impl<DataType: DataToCell<H>, H: ColumnHeader> DataTable<DataType, H, Unsortable
|
||||||
data: vec![],
|
data: vec![],
|
||||||
sort_type: Unsortable,
|
sort_type: Unsortable,
|
||||||
first_draw: true,
|
first_draw: true,
|
||||||
|
first_index: None,
|
||||||
_pd: PhantomData,
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,14 +65,20 @@ impl<DataType: DataToCell<H>, H: ColumnHeader> DataTable<DataType, H, Unsortable
|
||||||
impl<DataType: DataToCell<H>, H: ColumnHeader, S: SortType, C: DataTableColumn<H>>
|
impl<DataType: DataToCell<H>, H: ColumnHeader, S: SortType, C: DataTableColumn<H>>
|
||||||
DataTable<DataType, H, S, C>
|
DataTable<DataType, H, S, C>
|
||||||
{
|
{
|
||||||
|
/// Sets the default value selected on first initialization, if possible.
|
||||||
|
pub fn first_draw_index(mut self, first_index: usize) -> Self {
|
||||||
|
self.first_index = Some(first_index);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the scroll position to the first value.
|
/// Sets the scroll position to the first value.
|
||||||
pub fn set_first(&mut self) {
|
pub fn to_first(&mut self) {
|
||||||
self.state.current_index = 0;
|
self.state.current_index = 0;
|
||||||
self.state.scroll_direction = ScrollDirection::Up;
|
self.state.scroll_direction = ScrollDirection::Up;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the scroll position to the last value.
|
/// Sets the scroll position to the last value.
|
||||||
pub fn set_last(&mut self) {
|
pub fn to_last(&mut self) {
|
||||||
self.state.current_index = self.data.len().saturating_sub(1);
|
self.state.current_index = self.data.len().saturating_sub(1);
|
||||||
self.state.scroll_direction = ScrollDirection::Down;
|
self.state.scroll_direction = ScrollDirection::Down;
|
||||||
}
|
}
|
||||||
|
@ -189,11 +197,11 @@ mod test {
|
||||||
let mut table = DataTable::new(columns, props, styling);
|
let mut table = DataTable::new(columns, props, styling);
|
||||||
table.set_data((0..=4).map(|index| TestType { index }).collect::<Vec<_>>());
|
table.set_data((0..=4).map(|index| TestType { index }).collect::<Vec<_>>());
|
||||||
|
|
||||||
table.set_last();
|
table.to_last();
|
||||||
assert_eq!(table.current_index(), 4);
|
assert_eq!(table.current_index(), 4);
|
||||||
assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
|
assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
|
||||||
|
|
||||||
table.set_first();
|
table.to_first();
|
||||||
assert_eq!(table.current_index(), 0);
|
assert_eq!(table.current_index(), 0);
|
||||||
assert_eq!(table.state.scroll_direction, ScrollDirection::Up);
|
assert_eq!(table.state.scroll_direction, ScrollDirection::Up);
|
||||||
|
|
||||||
|
|
|
@ -200,10 +200,15 @@ where
|
||||||
self.props.table_gap
|
self.props.table_gap
|
||||||
};
|
};
|
||||||
|
|
||||||
let columns = &self.columns;
|
|
||||||
if !self.data.is_empty() || !self.first_draw {
|
if !self.data.is_empty() || !self.first_draw {
|
||||||
self.first_draw = false; // TODO: Doing it this way is fine, but it could be done better (e.g. showing custom no results/entries message)
|
if self.first_draw {
|
||||||
|
self.first_draw = false; // TODO: Doing it this way is fine, but it could be done better (e.g. showing custom no results/entries message)
|
||||||
|
if let Some(first_index) = self.first_index {
|
||||||
|
self.set_position(first_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let columns = &self.columns;
|
||||||
let rows = {
|
let rows = {
|
||||||
let num_rows =
|
let num_rows =
|
||||||
usize::from(inner_height.saturating_sub(table_gap + header_height));
|
usize::from(inner_height.saturating_sub(table_gap + header_height));
|
||||||
|
|
|
@ -260,6 +260,7 @@ where
|
||||||
order: props.order,
|
order: props.order,
|
||||||
},
|
},
|
||||||
first_draw: true,
|
first_draw: true,
|
||||||
|
first_index: None,
|
||||||
data: vec![],
|
data: vec![],
|
||||||
_pd: PhantomData,
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
|
|
|
@ -598,6 +598,10 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
|
||||||
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%
|
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%
|
||||||
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"]
|
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"]
|
||||||
|
|
||||||
|
# [cpu]
|
||||||
|
# One of "all" (default), "average"/"avg"
|
||||||
|
# default = "average"
|
||||||
|
|
||||||
# These are all the components that support custom theming. Note that colour support
|
# These are all the components that support custom theming. Note that colour support
|
||||||
# will depend on terminal support.
|
# will depend on terminal support.
|
||||||
#[colors] # Uncomment if you want to use custom colors
|
#[colors] # Uncomment if you want to use custom colors
|
||||||
|
|
|
@ -28,8 +28,11 @@ use crate::{
|
||||||
|
|
||||||
pub mod layout_options;
|
pub mod layout_options;
|
||||||
|
|
||||||
pub mod process_columns;
|
mod process_columns;
|
||||||
use self::process_columns::ProcessConfig;
|
pub use process_columns::ProcessConfig;
|
||||||
|
|
||||||
|
mod cpu;
|
||||||
|
pub use cpu::{CpuConfig, CpuDefault};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
|
@ -43,6 +46,7 @@ pub struct Config {
|
||||||
pub temp_filter: Option<IgnoreList>,
|
pub temp_filter: Option<IgnoreList>,
|
||||||
pub net_filter: Option<IgnoreList>,
|
pub net_filter: Option<IgnoreList>,
|
||||||
pub processes: Option<ProcessConfig>,
|
pub processes: Option<ProcessConfig>,
|
||||||
|
pub cpu: Option<CpuConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
@ -340,6 +344,11 @@ pub fn build_app(
|
||||||
widget.widget_id,
|
widget.widget_id,
|
||||||
CpuWidgetState::new(
|
CpuWidgetState::new(
|
||||||
&app_config_fields,
|
&app_config_fields,
|
||||||
|
config
|
||||||
|
.cpu
|
||||||
|
.as_ref()
|
||||||
|
.map(|cfg| cfg.default)
|
||||||
|
.unwrap_or_default(),
|
||||||
default_time_value,
|
default_time_value,
|
||||||
autohide_timer,
|
autohide_timer,
|
||||||
styling,
|
styling,
|
||||||
|
|
79
src/options/cpu.rs
Normal file
79
src/options/cpu.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// The default selection of the CPU widget. If the given selection is invalid, we will fall back to all.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum CpuDefault {
|
||||||
|
#[default]
|
||||||
|
All,
|
||||||
|
#[serde(alias = "avg")]
|
||||||
|
Average,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process column settings.
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize)]
|
||||||
|
pub struct CpuConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub default: CpuDefault,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_cpu_default() {
|
||||||
|
let config = "";
|
||||||
|
let generated: CpuConfig = toml_edit::de::from_str(config).unwrap();
|
||||||
|
match generated.default {
|
||||||
|
CpuDefault::All => {}
|
||||||
|
CpuDefault::Average => {
|
||||||
|
panic!("the default should be all")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_cpu_default() {
|
||||||
|
let config = r#"
|
||||||
|
default = "all"
|
||||||
|
"#;
|
||||||
|
let generated: CpuConfig = toml_edit::de::from_str(config).unwrap();
|
||||||
|
match generated.default {
|
||||||
|
CpuDefault::All => {}
|
||||||
|
CpuDefault::Average => {
|
||||||
|
panic!("the default should be all")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn avg_cpu_default() {
|
||||||
|
let config = r#"
|
||||||
|
default = "avg"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let generated: CpuConfig = toml_edit::de::from_str(config).unwrap();
|
||||||
|
match generated.default {
|
||||||
|
CpuDefault::All => {
|
||||||
|
panic!("the avg should be set")
|
||||||
|
}
|
||||||
|
CpuDefault::Average => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn average_cpu_default() {
|
||||||
|
let config = r#"
|
||||||
|
default = "average"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let generated: CpuConfig = toml_edit::de::from_str(config).unwrap();
|
||||||
|
match generated.default {
|
||||||
|
CpuDefault::All => {
|
||||||
|
panic!("the avg should be set")
|
||||||
|
}
|
||||||
|
CpuDefault::Average => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ use crate::{
|
||||||
DataToCell,
|
DataToCell,
|
||||||
},
|
},
|
||||||
data_conversion::CpuWidgetData,
|
data_conversion::CpuWidgetData,
|
||||||
|
options::CpuDefault,
|
||||||
utils::gen_util::truncate_to_text,
|
utils::gen_util::truncate_to_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -165,8 +166,8 @@ pub struct CpuWidgetState {
|
||||||
|
|
||||||
impl CpuWidgetState {
|
impl CpuWidgetState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: &AppConfigFields, current_display_time: u64, autohide_timer: Option<Instant>,
|
config: &AppConfigFields, default_selection: CpuDefault, current_display_time: u64,
|
||||||
colours: &CanvasStyling,
|
autohide_timer: Option<Instant>, colours: &CanvasStyling,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
const COLUMNS: [Column<CpuWidgetColumn>; 2] = [
|
const COLUMNS: [Column<CpuWidgetColumn>; 2] = [
|
||||||
Column::soft(CpuWidgetColumn::CPU, Some(0.5)),
|
Column::soft(CpuWidgetColumn::CPU, Some(0.5)),
|
||||||
|
@ -183,13 +184,21 @@ impl CpuWidgetState {
|
||||||
};
|
};
|
||||||
|
|
||||||
let styling = DataTableStyling::from_colours(colours);
|
let styling = DataTableStyling::from_colours(colours);
|
||||||
|
let mut table = DataTable::new(COLUMNS, props, styling);
|
||||||
|
match default_selection {
|
||||||
|
CpuDefault::All => {}
|
||||||
|
CpuDefault::Average if !config.show_average_cpu => {}
|
||||||
|
CpuDefault::Average => {
|
||||||
|
table = table.first_draw_index(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CpuWidgetState {
|
CpuWidgetState {
|
||||||
current_display_time,
|
current_display_time,
|
||||||
is_legend_hidden: false,
|
is_legend_hidden: false,
|
||||||
show_avg: config.show_average_cpu,
|
show_avg: config.show_average_cpu,
|
||||||
autohide_timer,
|
autohide_timer,
|
||||||
table: DataTable::new(COLUMNS, props, styling),
|
table,
|
||||||
styling: CpuWidgetStyling::from_colours(colours),
|
styling: CpuWidgetStyling::from_colours(colours),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue