diff --git a/.vscode/settings.json b/.vscode/settings.json index e03fdc59d..531704fbb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,6 +40,7 @@ "crossterm", "curr", "czvf", + "denylist", "fpath", "fract", "gnueabihf", @@ -57,6 +58,7 @@ "noheader", "ntdef", "nuget", + "nvme", "paren", "pmem", "prepush", @@ -82,6 +84,7 @@ "virt", "vsize", "whitespaces", + "wifi", "winapi", "winget", "winnt", diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f064ad8f..5762280bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#208](https://github.com/ClementTsang/bottom/pull/208): Mouse support for tables and moving to widgets. +- [#217](https://github.com/ClementTsang/bottom/pull/217): Unofficial ARM support. + +- [#220](https://github.com/ClementTsang/bottom/pull/220): Add ability to hide specific temperature and disk entries via config. + ### Changes - [#213](https://github.com/ClementTsang/bottom/pull/213), [#214](https://github.com/ClementTsang/bottom/pull/214): Updated help descriptions, added auto-complete generation. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb948b3e3..2206f95d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,10 +46,9 @@ If you want to help contribute by submitting a PR, by all means, I'm open! In re - You can check clippy using `cargo clippy`. - - I use [cargo-husky](https://github.com/rhysd/cargo-husky) to automatically run a clippy check on push. You can disable this in the `Cargo.toml` file if you find this annoying. + - I use [cargo-husky](https://github.com/rhysd/cargo-husky) to automatically run a `cargo clippy` and `cargo test` check. -- You may notice that I have fern and log as dependencies; this is mostly for easy debugging via the `debug!()` macro. It writes to the - `debug.log` file that will automatically be created if you run in debug mode (so `cargo run`). +- You may notice that I have fern and log as dependencies; this is mostly for easy debugging via the `debug!()` macro. It writes to the `debug.log` file that will automatically be created if you run in debug mode (so `cargo run`). And in regards to the pull request process: diff --git a/README.md b/README.md index a8a39388d..0a19dde77 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ A cross-platform graphical process/system monitor with a customizable interface - [Config flags](#config-flags) - [Theming](#theming) - [Layout](#layout) + - [Disk and temperature filtering](#disk-and-temperature-filtering) - [Battery](#battery) - [Compatibility](#compatibility) - [Contribution](#contribution) @@ -352,16 +353,16 @@ Note that the `and` operator takes precedence over the `or` operator. #### General -| | | -| ------------ | --------------------------------------------------------------------------------------------------------------------- | -| Mouse scroll | Table: Scroll
Chart: Zooms in or out by scrolling up or down respectively | -| Mouse click | Selects the clicked widget. For tables, clicking can also select a specific entry. Can be disabled via options/flags. | +| | | +| ------ | ---------------------------------------------------------------------------------------------------------------- | +| Scroll | Table: Scroll
Chart: Zooms in or out by scrolling up or down respectively | +| Click | Selects the clicked widget. For tables, clicking can also select an entry.
Can be disabled via options/flags. | #### CPU bindings -| | | -| ------------ | --------------------------------------------------------------------- | -| Mouse scroll | Scrolling over an CPU core/average shows only that entry on the chart | +| | | +| ------ | --------------------------------------------------------------------- | +| Scroll | Scrolling over an CPU core/average shows only that entry on the chart | ## Features @@ -596,6 +597,48 @@ Furthermore, you can have duplicate widgets. This means you could do something l and get the following CPU donut: ![CPU donut](./assets/cpu_layout.png) +#### Disk and temperature filtering + +You can hide specific disks and temperature sensors by name in the config file via `disk_filter` and `temp_filter` respectively. Regex (`regex = true`) and case-sensitivity (`case_sensitive = true`) are supported, but are off by default. + +For example, let's say , given this disk list: + +![Disk filter not ignoring list](./assets/disk_filter_pre.png) + +I wish to _only_ show disks that follow the form `/dev/sda\d+`, or `/dev/nvme0n1p2`: + +```toml +[disk_filter] +is_list_ignored = false +list = ["/dev/sda\\d+", "/dev/nvme0n1p2"] +regex = true +``` + +![Disk filter not ignoring list](./assets/disk_filter_post.png) + +This would ignore anything that does not match either of these two conditions. If I instead wish to ignore anything that matches this list, then I can set `is_list_ignored = true` instead: + +![Disk filter ignoring list](./assets/disk_filter_post2.png) + +Likewise, I can do something similar for `temp_filter`: + +![Temp filter before](./assets/temp_filter_pre.png) + +If I, say, only wanted to see any entry with the words "cpu" or "wifi" in it, case sensitive: + +```toml +[temp_filter] +is_list_ignored = false +list = ["cpu", "wifi"] +case_sensitive = true +``` + +![Temp filter after](./assets/temp_filter_post.png) + +Now, flipping to `case_sensitive = false` would instead show: + +![Temp filter after with case sensitivity off](./assets/temp_filter_post2.png) + ### Battery You can get battery statistics (charge, time to fill/discharge, consumption in watts, and battery health) via the battery widget. diff --git a/assets/disk_filter_post.png b/assets/disk_filter_post.png new file mode 100644 index 000000000..741976d85 Binary files /dev/null and b/assets/disk_filter_post.png differ diff --git a/assets/disk_filter_post2.png b/assets/disk_filter_post2.png new file mode 100644 index 000000000..09c06de27 Binary files /dev/null and b/assets/disk_filter_post2.png differ diff --git a/assets/disk_filter_pre.png b/assets/disk_filter_pre.png new file mode 100644 index 000000000..3b8463b05 Binary files /dev/null and b/assets/disk_filter_pre.png differ diff --git a/assets/summary_and_search.gif b/assets/summary_and_search.gif index 730d07e10..d50a03556 100644 Binary files a/assets/summary_and_search.gif and b/assets/summary_and_search.gif differ diff --git a/assets/temp_filter_post.png b/assets/temp_filter_post.png new file mode 100644 index 000000000..23ac208ea Binary files /dev/null and b/assets/temp_filter_post.png differ diff --git a/assets/temp_filter_post2.png b/assets/temp_filter_post2.png new file mode 100644 index 000000000..88f40e64d Binary files /dev/null and b/assets/temp_filter_post2.png differ diff --git a/assets/temp_filter_pre.png b/assets/temp_filter_pre.png new file mode 100644 index 000000000..41a6282bb Binary files /dev/null and b/assets/temp_filter_pre.png differ diff --git a/src/app.rs b/src/app.rs index 8893a1c73..9ceb9d560 100644 --- a/src/app.rs +++ b/src/app.rs @@ -43,6 +43,18 @@ pub struct AppConfigFields { pub disable_click: bool, } +/// For filtering out information +pub struct DataFilters { + pub disk_filter: Option, + pub temp_filter: Option, +} + +#[derive(Debug)] +pub struct Filter { + pub is_list_ignored: bool, + pub list: Vec, +} + #[derive(TypedBuilder)] pub struct App { #[builder(default = false, setter(skip))] @@ -99,6 +111,7 @@ pub struct App { pub widget_map: HashMap, pub current_widget: BottomWidget, pub used_widgets: UsedWidgets, + pub filters: DataFilters, } impl App { @@ -312,10 +325,9 @@ impl App { pub fn toggle_sort(&mut self) { match &self.current_widget.widget_type { - // FIXME: [REFACTOR] Remove these @'s if unneeded, they were an idea but they're ultimately useless for me here...? - widget_type @ BottomWidgetType::Proc | widget_type @ BottomWidgetType::ProcSort => { + BottomWidgetType::Proc | BottomWidgetType::ProcSort => { let widget_id = self.current_widget.widget_id - - match &widget_type { + - match &self.current_widget.widget_type { BottomWidgetType::Proc => 0, BottomWidgetType::ProcSort => 2, _ => 0, @@ -348,9 +360,9 @@ impl App { pub fn invert_sort(&mut self) { match &self.current_widget.widget_type { - widget_type @ BottomWidgetType::Proc | widget_type @ BottomWidgetType::ProcSort => { + BottomWidgetType::Proc | BottomWidgetType::ProcSort => { let widget_id = self.current_widget.widget_id - - match &widget_type { + - match &self.current_widget.widget_type { BottomWidgetType::Proc => 0, BottomWidgetType::ProcSort => 2, _ => 0, @@ -1571,9 +1583,9 @@ impl App { } } WidgetDirection::Down => match &self.current_widget.widget_type { - proc_type @ BottomWidgetType::Proc | proc_type @ BottomWidgetType::ProcSort => { + BottomWidgetType::Proc | BottomWidgetType::ProcSort => { let widget_id = self.current_widget.widget_id - - match proc_type { + - match &self.current_widget.widget_type { BottomWidgetType::ProcSort => 2, _ => 0, }; diff --git a/src/app/data_harvester/disks.rs b/src/app/data_harvester/disks.rs index 948279e5c..788f05cee 100644 --- a/src/app/data_harvester/disks.rs +++ b/src/app/data_harvester/disks.rs @@ -46,7 +46,9 @@ pub async fn get_sysinfo_disk_usage_list( name: disk.get_name().to_string_lossy().into(), mount_point: disk.get_mount_point().to_string_lossy().into(), free_space: disk.get_available_space(), - used_space: disk.get_total_space() - disk.get_available_space(), + used_space: disk + .get_total_space() + .saturating_sub(disk.get_available_space()), total_space: disk.get_total_space(), }) .collect::>(); diff --git a/src/bin/main.rs b/src/bin/main.rs index 7f8b99003..dd1cbabef 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -83,10 +83,7 @@ fn main() -> Result<()> { create_event_thread( sender, reset_receiver, - app.app_config_fields.use_current_cpu_total, - app.app_config_fields.update_rate_in_milliseconds, - app.app_config_fields.temperature_type.clone(), - app.app_config_fields.show_average_cpu, + &app.app_config_fields, app.used_widgets.clone(), ); @@ -151,7 +148,8 @@ fn main() -> Result<()> { // Disk if app.used_widgets.use_disk { - app.canvas_data.disk_data = convert_disk_row(&app.data_collection); + app.canvas_data.disk_data = + convert_disk_row(&app.data_collection, &app.filters.disk_filter); } // Temperatures diff --git a/src/canvas.rs b/src/canvas.rs index 7036bd49c..61f74e4f9 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -379,9 +379,9 @@ impl Painter { app_state.current_widget.widget_id, false, ), - proc_type @ Proc | proc_type @ ProcSearch | proc_type @ ProcSort => { + Proc | ProcSearch | ProcSort => { let widget_id = app_state.current_widget.widget_id - - match proc_type { + - match &app_state.current_widget.widget_type { ProcSearch => 1, ProcSort => 2, _ => 0, diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index 8a75ebd6d..32887c322 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -67,7 +67,6 @@ impl NetworkGraphWidget for Painter { // Update draw loc in widget map // Note that in both cases, we always go to the same widget id so it's fine to do it like // this lol. - debug!("!@#!@"); if let Some(network_widget) = app_state.widget_map.get_mut(&widget_id) { network_widget.top_left_corner = Some((draw_loc.x, draw_loc.y)); network_widget.bottom_right_corner = diff --git a/src/constants.rs b/src/constants.rs index d17724132..5d58848c4 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -248,6 +248,7 @@ pub const DEFAULT_BATTERY_LAYOUT: &str = r##" pub const DEFAULT_CONFIG_FILE_PATH: &str = "bottom/bottom.toml"; // Default config file +// FIXME: Update the default config pub const DEFAULT_CONFIG_CONTENT: &str = r##" # This is a default config file for bottom. All of the settings are commented # out by default; if you wish to change them uncomment and modify as you see diff --git a/src/data_conversion.rs b/src/data_conversion.rs index eaf655d00..bf38483e1 100644 --- a/src/data_conversion.rs +++ b/src/data_conversion.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::{ - app::{data_farmer, data_harvester, App}, + app::{data_farmer, data_harvester, App, Filter}, utils::gen_util::*, }; @@ -83,40 +83,77 @@ pub struct ConvertedCpuData { } pub fn convert_temp_row(app: &App) -> Vec> { - let mut sensor_vector: Vec> = Vec::new(); - let current_data = &app.data_collection; let temp_type = &app.app_config_fields.temperature_type; + let temp_filter = &app.filters.temp_filter; - if current_data.temp_harvest.is_empty() { - sensor_vector.push(vec!["No Sensors Found".to_string(), "".to_string()]) - } else { - for sensor in ¤t_data.temp_harvest { - sensor_vector.push(vec![ - match (&sensor.component_name, &sensor.component_label) { - (Some(name), Some(label)) => format!("{}: {}", name, label), - (None, Some(label)) => label.to_string(), - (Some(name), None) => name.to_string(), - (None, None) => String::default(), - }, - (sensor.temperature.ceil() as u64).to_string() - + match temp_type { - data_harvester::temperature::TemperatureType::Celsius => "C", - data_harvester::temperature::TemperatureType::Kelvin => "K", - data_harvester::temperature::TemperatureType::Fahrenheit => "F", - }, - ]); - } + let mut sensor_vector: Vec> = current_data + .temp_harvest + .iter() + .filter_map(|temp_harvest| { + let name = match (&temp_harvest.component_name, &temp_harvest.component_label) { + (Some(name), Some(label)) => format!("{}: {}", name, label), + (None, Some(label)) => label.to_string(), + (Some(name), None) => name.to_string(), + (None, None) => String::default(), + }; + + let to_keep = if let Some(temp_filter) = temp_filter { + let mut ret = temp_filter.is_list_ignored; + for r in &temp_filter.list { + if r.is_match(&name) { + ret = !temp_filter.is_list_ignored; + break; + } + } + ret + } else { + true + }; + + if to_keep { + Some(vec![ + name, + (temp_harvest.temperature.ceil() as u64).to_string() + + match temp_type { + data_harvester::temperature::TemperatureType::Celsius => "C", + data_harvester::temperature::TemperatureType::Kelvin => "K", + data_harvester::temperature::TemperatureType::Fahrenheit => "F", + }, + ]) + } else { + None + } + }) + .collect(); + + if sensor_vector.is_empty() { + sensor_vector.push(vec!["No Sensors Found".to_string(), "".to_string()]); } sensor_vector } -pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec> { +pub fn convert_disk_row( + current_data: &data_farmer::DataCollection, disk_filter: &Option, +) -> Vec> { let mut disk_vector: Vec> = Vec::new(); + current_data .disk_harvest .iter() + .filter(|disk_harvest| { + if let Some(disk_filter) = disk_filter { + for r in &disk_filter.list { + if r.is_match(&disk_harvest.name) { + return !disk_filter.is_list_ignored; + } + } + disk_filter.is_list_ignored + } else { + true + } + }) .zip(¤t_data.io_labels) .for_each(|(disk, (io_read, io_write))| { let converted_free_space = get_simple_byte_values(disk.free_space, false); diff --git a/src/lib.rs b/src/lib.rs index 207c2963e..1b1c3e7bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -687,16 +687,21 @@ pub fn create_event_thread( sender: std::sync::mpsc::Sender< BottomEvent, >, - reset_receiver: std::sync::mpsc::Receiver, use_current_cpu_total: bool, - update_rate_in_milliseconds: u64, temp_type: data_harvester::temperature::TemperatureType, - show_average_cpu: bool, used_widget_set: UsedWidgets, + reset_receiver: std::sync::mpsc::Receiver, + app_config_fields: &app::AppConfigFields, used_widget_set: UsedWidgets, ) { + let temp_type = app_config_fields.temperature_type.clone(); + let use_current_cpu_total = app_config_fields.use_current_cpu_total; + let show_average_cpu = app_config_fields.show_average_cpu; + let update_rate_in_milliseconds = app_config_fields.update_rate_in_milliseconds; + thread::spawn(move || { let mut data_state = data_harvester::DataCollector::default(); data_state.set_collected_data(used_widget_set); data_state.set_temperature_type(temp_type); data_state.set_use_current_cpu_total(use_current_cpu_total); data_state.set_show_average_cpu(show_average_cpu); + data_state.init(); loop { if let Ok(message) = reset_receiver.try_recv() { diff --git a/src/options.rs b/src/options.rs index e1a6e226f..a8536d8aa 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,3 +1,4 @@ +use regex::Regex; use serde::Deserialize; use std::collections::{HashMap, HashSet}; use std::time::Instant; @@ -19,6 +20,8 @@ pub struct Config { pub flags: Option, pub colors: Option, pub row: Option>, + pub disk_filter: Option, + pub temp_filter: Option, } #[derive(Default, Deserialize)] @@ -69,6 +72,14 @@ pub struct ConfigColours { pub battery_colors: Option>, } +#[derive(Default, Deserialize)] +pub struct IgnoreList { + pub is_list_ignored: bool, + pub list: Vec, + pub regex: Option, + pub case_sensitive: Option, +} + pub fn build_app( matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout, default_widget_id: u64, default_widget_type_option: &Option, @@ -249,6 +260,11 @@ pub fn build_app( use_battery: used_widget_set.get(&Battery).is_some(), }; + let disk_filter = + get_ignore_list(&config.disk_filter).context("Update 'disk_filter' in your config file")?; + let temp_filter = + get_ignore_list(&config.temp_filter).context("Update 'temp_filter' in your config file")?; + Ok(App::builder() .app_config_fields(app_config_fields) .cpu_state(CpuState::init(cpu_state_map)) @@ -262,6 +278,10 @@ pub fn build_app( .current_widget(widget_map.get(&initial_widget_id).unwrap().clone()) // I think the unwrap is fine here .widget_map(widget_map) .used_widgets(used_widgets) + .filters(DataFilters { + disk_filter, + temp_filter, + }) .build()) } @@ -665,3 +685,45 @@ pub fn get_use_battery(matches: &clap::ArgMatches<'static>, config: &Config) -> } false } + +pub fn get_ignore_list(ignore_list: &Option) -> error::Result> { + if let Some(ignore_list) = ignore_list { + let list: Result, _> = ignore_list + .list + .iter() + .map(|name| { + let use_regex = if let Some(use_regex) = ignore_list.regex { + use_regex + } else { + false + }; + let use_cs = if let Some(use_cs) = ignore_list.case_sensitive { + use_cs + } else { + false + }; + + let escaped_string: String; + let res = format!( + "{}{}", + if use_cs { "" } else { "(?i)" }, + if use_regex { + name + } else { + escaped_string = regex::escape(name); + &escaped_string + } + ); + + Regex::new(&res) + }) + .collect(); + + Ok(Some(Filter { + list: list?, + is_list_ignored: ignore_list.is_list_ignored, + })) + } else { + Ok(None) + } +}