mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-07 22:04:43 +02:00
Major refactoring to appease clippy; potential reintroduction of hjkl keys to navigate widgets...
This commit is contained in:
parent
7ec52b722b
commit
60b6a0911a
@ -48,7 +48,9 @@ TBD
|
|||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
You may need to install a font like [FreeMono](https://fonts2u.com/free-monospaced.font) and use a terminal like cmder for font support to work properly, unfortunately. I plan to add a Chocolatey install option in the future.
|
I advise running the program with the `--dot_marker` or `-m` option, as the braille font seems to not work out of the box on Powershell. You may need to install a font like [FreeMono](https://fonts2u.com/free-monospaced.font) and use a terminal like cmder for font support to work properly, unfortunately.
|
||||||
|
|
||||||
|
I plan to add a Chocolatey install option in the future.
|
||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
|
|
||||||
|
12
src/app.rs
12
src/app.rs
@ -426,12 +426,10 @@ impl App {
|
|||||||
self.search_state.current_cursor_position -= 1;
|
self.search_state.current_cursor_position -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if self.delete_dialog_state.is_showing_dd && !self.delete_dialog_state.is_on_yes {
|
||||||
if self.delete_dialog_state.is_showing_dd && !self.delete_dialog_state.is_on_yes {
|
|
||||||
self.delete_dialog_state.is_on_yes = true;
|
self.delete_dialog_state.is_on_yes = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_right_key(&mut self) {
|
pub fn on_right_key(&mut self) {
|
||||||
if !self.is_in_dialog() {
|
if !self.is_in_dialog() {
|
||||||
@ -442,12 +440,10 @@ impl App {
|
|||||||
self.search_state.current_cursor_position += 1;
|
self.search_state.current_cursor_position += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if self.delete_dialog_state.is_showing_dd && self.delete_dialog_state.is_on_yes {
|
||||||
if self.delete_dialog_state.is_showing_dd && self.delete_dialog_state.is_on_yes {
|
|
||||||
self.delete_dialog_state.is_on_yes = false;
|
self.delete_dialog_state.is_on_yes = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn skip_cursor_beginning(&mut self) {
|
pub fn skip_cursor_beginning(&mut self) {
|
||||||
if !self.is_in_dialog() {
|
if !self.is_in_dialog() {
|
||||||
@ -615,8 +611,7 @@ impl App {
|
|||||||
self.awaiting_second_char = false;
|
self.awaiting_second_char = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
if self.help_dialog_state.is_showing_help {
|
|
||||||
match caught_char {
|
match caught_char {
|
||||||
'1' => self.help_dialog_state.current_category = AppHelpCategory::General,
|
'1' => self.help_dialog_state.current_category = AppHelpCategory::General,
|
||||||
'2' => self.help_dialog_state.current_category = AppHelpCategory::Process,
|
'2' => self.help_dialog_state.current_category = AppHelpCategory::Process,
|
||||||
@ -625,7 +620,6 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn kill_highlighted_process(&mut self) -> Result<()> {
|
pub fn kill_highlighted_process(&mut self) -> Result<()> {
|
||||||
// Technically unnecessary but this is a good check...
|
// Technically unnecessary but this is a good check...
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use crate::utils::error;
|
use crate::utils::error;
|
||||||
use std::{collections::HashMap, process::Command, time::Instant};
|
use std::{
|
||||||
|
collections::{hash_map::RandomState, HashMap},
|
||||||
|
process::Command,
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
use sysinfo::{ProcessExt, System, SystemExt};
|
use sysinfo::{ProcessExt, System, SystemExt};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -111,10 +115,10 @@ fn get_process_cpu_stats(pid: u32) -> std::io::Result<f64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Note that cpu_percentage should be represented WITHOUT the \times 100 factor!
|
/// Note that cpu_percentage should be represented WITHOUT the \times 100 factor!
|
||||||
fn linux_cpu_usage(
|
fn linux_cpu_usage<S: core::hash::BuildHasher>(
|
||||||
pid: u32, cpu_usage: f64, cpu_percentage: f64,
|
pid: u32, cpu_usage: f64, cpu_percentage: f64,
|
||||||
prev_pid_stats: &HashMap<String, (f64, Instant)>,
|
prev_pid_stats: &HashMap<String, (f64, Instant), S>,
|
||||||
new_pid_stats: &mut HashMap<String, (f64, Instant)>, use_current_cpu_total: bool,
|
new_pid_stats: &mut HashMap<String, (f64, Instant), S>, use_current_cpu_total: bool,
|
||||||
curr_time: &Instant,
|
curr_time: &Instant,
|
||||||
) -> std::io::Result<f64> {
|
) -> std::io::Result<f64> {
|
||||||
// Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556
|
// Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556
|
||||||
@ -145,10 +149,10 @@ fn linux_cpu_usage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_ps(
|
fn convert_ps<S: core::hash::BuildHasher>(
|
||||||
process: &str, cpu_usage: f64, cpu_percentage: f64,
|
process: &str, cpu_usage: f64, cpu_percentage: f64,
|
||||||
prev_pid_stats: &HashMap<String, (f64, Instant)>,
|
prev_pid_stats: &HashMap<String, (f64, Instant), S>,
|
||||||
new_pid_stats: &mut HashMap<String, (f64, Instant)>, use_current_cpu_total: bool,
|
new_pid_stats: &mut HashMap<String, (f64, Instant), S>, use_current_cpu_total: bool,
|
||||||
curr_time: &Instant,
|
curr_time: &Instant,
|
||||||
) -> std::io::Result<ProcessHarvest> {
|
) -> std::io::Result<ProcessHarvest> {
|
||||||
if process.trim().to_string().is_empty() {
|
if process.trim().to_string().is_empty() {
|
||||||
@ -190,7 +194,7 @@ fn convert_ps(
|
|||||||
|
|
||||||
pub fn get_sorted_processes_list(
|
pub fn get_sorted_processes_list(
|
||||||
sys: &System, prev_idle: &mut f64, prev_non_idle: &mut f64,
|
sys: &System, prev_idle: &mut f64, prev_non_idle: &mut f64,
|
||||||
prev_pid_stats: &mut HashMap<String, (f64, Instant)>, use_current_cpu_total: bool,
|
prev_pid_stats: &mut HashMap<String, (f64, Instant), RandomState>, use_current_cpu_total: bool,
|
||||||
mem_total_kb: u64, curr_time: &Instant,
|
mem_total_kb: u64, curr_time: &Instant,
|
||||||
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
|
||||||
let mut process_vector: Vec<ProcessHarvest> = Vec::new();
|
let mut process_vector: Vec<ProcessHarvest> = Vec::new();
|
||||||
@ -207,7 +211,7 @@ pub fn get_sorted_processes_list(
|
|||||||
if let Ok((cpu_usage, cpu_percentage)) = cpu_calc {
|
if let Ok((cpu_usage, cpu_percentage)) = cpu_calc {
|
||||||
let process_stream = split_string.collect::<Vec<&str>>();
|
let process_stream = split_string.collect::<Vec<&str>>();
|
||||||
|
|
||||||
let mut new_pid_stats: HashMap<String, (f64, Instant)> = HashMap::new();
|
let mut new_pid_stats: HashMap<String, (f64, Instant), RandomState> = HashMap::new();
|
||||||
|
|
||||||
for process in process_stream {
|
for process in process_stream {
|
||||||
if let Ok(process_object) = convert_ps(
|
if let Ok(process_object) = convert_ps(
|
||||||
|
@ -588,7 +588,7 @@ impl Painter {
|
|||||||
.header_style(self.colours.table_header_style)
|
.header_style(self.colours.table_header_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(intrinsic_widths
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
)
|
)
|
||||||
@ -678,7 +678,10 @@ impl Painter {
|
|||||||
.y_axis(y_axis)
|
.y_axis(y_axis)
|
||||||
.datasets(&[
|
.datasets(&[
|
||||||
Dataset::default()
|
Dataset::default()
|
||||||
.name("RX")
|
.name(&format!(
|
||||||
|
"RX: {:7}",
|
||||||
|
app_state.canvas_data.rx_display.clone()
|
||||||
|
))
|
||||||
.marker(if app_state.use_dot {
|
.marker(if app_state.use_dot {
|
||||||
Marker::Dot
|
Marker::Dot
|
||||||
} else {
|
} else {
|
||||||
@ -687,7 +690,10 @@ impl Painter {
|
|||||||
.style(self.colours.rx_style)
|
.style(self.colours.rx_style)
|
||||||
.data(&network_data_rx),
|
.data(&network_data_rx),
|
||||||
Dataset::default()
|
Dataset::default()
|
||||||
.name("TX")
|
.name(&format!(
|
||||||
|
"TX: {:7}",
|
||||||
|
app_state.canvas_data.tx_display.clone()
|
||||||
|
))
|
||||||
.marker(if app_state.use_dot {
|
.marker(if app_state.use_dot {
|
||||||
Marker::Dot
|
Marker::Dot
|
||||||
} else {
|
} else {
|
||||||
@ -742,7 +748,7 @@ impl Painter {
|
|||||||
.style(self.colours.text_style)
|
.style(self.colours.text_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(intrinsic_widths
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
)
|
)
|
||||||
@ -809,7 +815,7 @@ impl Painter {
|
|||||||
.header_style(self.colours.table_header_style)
|
.header_style(self.colours.table_header_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(intrinsic_widths
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
)
|
)
|
||||||
@ -876,7 +882,7 @@ impl Painter {
|
|||||||
.header_style(self.colours.table_header_style)
|
.header_style(self.colours.table_header_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(intrinsic_widths
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
)
|
)
|
||||||
@ -1122,7 +1128,7 @@ impl Painter {
|
|||||||
.header_style(self.colours.table_header_style)
|
.header_style(self.colours.table_header_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(intrinsic_widths
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
)
|
)
|
||||||
|
@ -78,11 +78,14 @@ impl CanvasColours {
|
|||||||
self.tx_style = Style::default().fg(convert_hex_to_color(hex)?);
|
self.tx_style = Style::default().fg(convert_hex_to_color(hex)?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn set_cpu_colours(&mut self, hex_colours: &Vec<String>) -> error::Result<()> {
|
pub fn set_cpu_colours(&mut self, hex_colours: &[String]) -> error::Result<()> {
|
||||||
let max_amount = std::cmp::min(hex_colours.len(), NUM_COLOURS as usize);
|
let max_amount = std::cmp::min(hex_colours.len(), NUM_COLOURS as usize);
|
||||||
for i in 0..max_amount {
|
for (itx, hex_colour) in hex_colours.iter().enumerate() {
|
||||||
|
if itx >= max_amount {
|
||||||
|
break;
|
||||||
|
}
|
||||||
self.cpu_colour_styles
|
self.cpu_colour_styles
|
||||||
.push(Style::default().fg(convert_hex_to_color(&hex_colours[i])?));
|
.push(Style::default().fg(convert_hex_to_color(hex_colour)?));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ pub const DEFAULT_UNIX_CONFIG_FILE_PATH: &str = "~/.config/btm/btm.toml";
|
|||||||
pub const DEFAULT_WINDOWS_CONFIG_FILE_PATH: &str = "";
|
pub const DEFAULT_WINDOWS_CONFIG_FILE_PATH: &str = "";
|
||||||
|
|
||||||
// Help text
|
// Help text
|
||||||
pub const GENERAL_HELP_TEXT: [&'static str; 14] = [
|
pub const GENERAL_HELP_TEXT: [&str; 14] = [
|
||||||
"General Keybindings\n\n",
|
"General Keybindings\n\n",
|
||||||
"Esc Close dialog box\n",
|
"Esc Close dialog box\n",
|
||||||
"q, Ctrl-c Quit bottom\n",
|
"q, Ctrl-c Quit bottom\n",
|
||||||
@ -29,7 +29,7 @@ pub const GENERAL_HELP_TEXT: [&'static str; 14] = [
|
|||||||
"G Skip to the last entry of a list\n",
|
"G Skip to the last entry of a list\n",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const PROCESS_HELP_TEXT: [&'static str; 8] = [
|
pub const PROCESS_HELP_TEXT: [&str; 8] = [
|
||||||
"Process Keybindings\n\n",
|
"Process Keybindings\n\n",
|
||||||
"dd Kill the highlighted process\n",
|
"dd Kill the highlighted process\n",
|
||||||
"c Sort by CPU usage\n",
|
"c Sort by CPU usage\n",
|
||||||
@ -40,7 +40,7 @@ pub const PROCESS_HELP_TEXT: [&'static str; 8] = [
|
|||||||
"Ctrl-f, / Open up the search widget\n",
|
"Ctrl-f, / Open up the search widget\n",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const SEARCH_HELP_TEXT: [&'static str; 8] = [
|
pub const SEARCH_HELP_TEXT: [&str; 8] = [
|
||||||
"Search Keybindings\n\n",
|
"Search Keybindings\n\n",
|
||||||
"Tab Toggle between searching for PID and name.\n",
|
"Tab Toggle between searching for PID and name.\n",
|
||||||
"Esc Close search widget\n",
|
"Esc Close search widget\n",
|
||||||
|
520
src/main.rs
520
src/main.rs
@ -53,13 +53,13 @@ enum ResetEvent {
|
|||||||
Reset,
|
Reset,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Default, Deserialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
flags: Option<ConfigFlags>,
|
flags: Option<ConfigFlags>,
|
||||||
colors: Option<ConfigColours>,
|
colors: Option<ConfigColours>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Default, Deserialize)]
|
||||||
struct ConfigFlags {
|
struct ConfigFlags {
|
||||||
avg_cpu: Option<bool>,
|
avg_cpu: Option<bool>,
|
||||||
dot_marker: Option<bool>,
|
dot_marker: Option<bool>,
|
||||||
@ -73,7 +73,7 @@ struct ConfigFlags {
|
|||||||
regex: Option<bool>,
|
regex: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Default, Deserialize)]
|
||||||
struct ConfigColours {
|
struct ConfigColours {
|
||||||
table_header_color: Option<String>,
|
table_header_color: Option<String>,
|
||||||
cpu_core_colors: Option<Vec<String>>,
|
cpu_core_colors: Option<Vec<String>>,
|
||||||
@ -90,9 +90,8 @@ struct ConfigColours {
|
|||||||
graph_color: Option<String>,
|
graph_color: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> error::Result<()> {
|
fn get_matches() -> clap::ArgMatches<'static> {
|
||||||
//Parse command line options
|
clap_app!(app =>
|
||||||
let matches = clap_app!(app =>
|
|
||||||
(name: crate_name!())
|
(name: crate_name!())
|
||||||
(version: crate_version!())
|
(version: crate_version!())
|
||||||
(author: crate_authors!())
|
(author: crate_authors!())
|
||||||
@ -114,123 +113,24 @@ fn main() -> error::Result<()> {
|
|||||||
(@arg WHOLE_WORD: -W --whole_word "Match whole word when searching by default.")
|
(@arg WHOLE_WORD: -W --whole_word "Match whole word when searching by default.")
|
||||||
(@arg REGEX_DEFAULT: -R --regex "Use regex in searching by default.")
|
(@arg REGEX_DEFAULT: -R --regex "Use regex in searching by default.")
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches()
|
||||||
|
}
|
||||||
|
|
||||||
if cfg!(debug_assertions) {
|
fn main() -> error::Result<()> {
|
||||||
utils::logging::init_logger()?;
|
create_logger()?;
|
||||||
}
|
let matches = get_matches();
|
||||||
|
|
||||||
let config_path = std::path::Path::new(matches.value_of("CONFIG_LOCATION").unwrap_or(
|
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
|
||||||
if cfg!(target_os = "windows") {
|
|
||||||
DEFAULT_WINDOWS_CONFIG_FILE_PATH
|
|
||||||
} else {
|
|
||||||
DEFAULT_UNIX_CONFIG_FILE_PATH
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
let config_string = std::fs::read_to_string(config_path);
|
let update_rate_in_milliseconds: u128 =
|
||||||
let config_toml: Config = if let Ok(config_str) = config_string {
|
get_update_rate_in_milliseconds(&matches.value_of("RATE_MILLIS"), &config)?;
|
||||||
toml::from_str(&config_str)?
|
|
||||||
} else {
|
|
||||||
toml::from_str("")?
|
|
||||||
};
|
|
||||||
|
|
||||||
let update_rate_in_milliseconds: u128 = if matches.is_present("RATE_MILLIS") {
|
|
||||||
matches
|
|
||||||
.value_of("RATE_MILLIS")
|
|
||||||
.unwrap_or(&DEFAULT_REFRESH_RATE_IN_MILLISECONDS.to_string())
|
|
||||||
.parse::<u128>()?
|
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
|
||||||
if let Some(rate) = flags.rate {
|
|
||||||
rate as u128
|
|
||||||
} else {
|
|
||||||
constants::DEFAULT_REFRESH_RATE_IN_MILLISECONDS
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
constants::DEFAULT_REFRESH_RATE_IN_MILLISECONDS
|
|
||||||
};
|
|
||||||
|
|
||||||
if update_rate_in_milliseconds < 250 {
|
|
||||||
return Err(BottomError::InvalidArg(
|
|
||||||
"Please set your update rate to be greater than 250 milliseconds.".to_string(),
|
|
||||||
));
|
|
||||||
} else if update_rate_in_milliseconds > u128::from(std::u64::MAX) {
|
|
||||||
return Err(BottomError::InvalidArg(
|
|
||||||
"Please set your update rate to be less than unsigned INT_MAX.".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set other settings
|
// Set other settings
|
||||||
let temperature_type = if matches.is_present("FAHRENHEIT") {
|
let temperature_type = get_temperature_option(&matches, &config)?;
|
||||||
data_harvester::temperature::TemperatureType::Fahrenheit
|
let show_average_cpu = get_avg_cpu_option(&matches, &config);
|
||||||
} else if matches.is_present("KELVIN") {
|
let use_dot = get_use_dot_option(&matches, &config);
|
||||||
data_harvester::temperature::TemperatureType::Kelvin
|
let left_legend = get_use_left_legend_option(&matches, &config);
|
||||||
} else if matches.is_present("CELSIUS") {
|
let use_current_cpu_total = get_use_current_cpu_total_option(&matches, &config);
|
||||||
data_harvester::temperature::TemperatureType::Celsius
|
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
|
||||||
if let Some(temp_type) = &flags.temperature_type {
|
|
||||||
// Give lowest priority to config.
|
|
||||||
match temp_type.as_str() {
|
|
||||||
"fahrenheit" | "f" => data_harvester::temperature::TemperatureType::Fahrenheit,
|
|
||||||
"kelvin" | "k" => data_harvester::temperature::TemperatureType::Kelvin,
|
|
||||||
"celsius" | "c" => data_harvester::temperature::TemperatureType::Celsius,
|
|
||||||
_ => {
|
|
||||||
return Err(BottomError::ConfigError(
|
|
||||||
"Invalid temperature type. Please have the value be of the form <kelvin|k|celsius|c|fahrenheit|f>".to_string()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data_harvester::temperature::TemperatureType::Celsius
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data_harvester::temperature::TemperatureType::Celsius
|
|
||||||
};
|
|
||||||
let show_average_cpu = if matches.is_present("AVG_CPU") {
|
|
||||||
true
|
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
|
||||||
if let Some(avg_cpu) = flags.avg_cpu {
|
|
||||||
avg_cpu
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
let use_dot = if matches.is_present("DOT_MARKER") {
|
|
||||||
true
|
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
|
||||||
if let Some(dot_marker) = flags.dot_marker {
|
|
||||||
dot_marker
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
let left_legend = if matches.is_present("LEFT_LEGEND") {
|
|
||||||
true
|
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
|
||||||
if let Some(left_legend) = flags.left_legend {
|
|
||||||
left_legend
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let use_current_cpu_total = if matches.is_present("USE_CURR_USAGE") {
|
|
||||||
true
|
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
|
||||||
if let Some(current_usage) = flags.current_usage {
|
|
||||||
current_usage
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create "app" struct, which will control most of the program and store settings/state
|
// Create "app" struct, which will control most of the program and store settings/state
|
||||||
let mut app = app::App::new(
|
let mut app = app::App::new(
|
||||||
@ -242,47 +142,10 @@ fn main() -> error::Result<()> {
|
|||||||
use_current_cpu_total,
|
use_current_cpu_total,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Enable grouping immediately if set.
|
enable_app_grouping(&matches, &config, &mut app);
|
||||||
if matches.is_present("GROUP_PROCESSES") {
|
enable_app_case_sensitive(&matches, &config, &mut app);
|
||||||
app.toggle_grouping();
|
enable_app_match_whole_word(&matches, &config, &mut app);
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
enable_app_use_regex(&matches, &config, &mut app);
|
||||||
if let Some(grouping) = flags.group_processes {
|
|
||||||
if grouping {
|
|
||||||
app.toggle_grouping();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set default search method
|
|
||||||
if matches.is_present("CASE_SENSITIVE") {
|
|
||||||
app.search_state.toggle_ignore_case();
|
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
|
||||||
if let Some(case_sensitive) = flags.case_sensitive {
|
|
||||||
if case_sensitive {
|
|
||||||
app.search_state.toggle_ignore_case();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches.is_present("WHOLE_WORD") {
|
|
||||||
app.search_state.toggle_search_whole_word();
|
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
|
||||||
if let Some(whole_word) = flags.whole_word {
|
|
||||||
if whole_word {
|
|
||||||
app.search_state.toggle_search_whole_word();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches.is_present("REGEX_DEFAULT") {
|
|
||||||
app.search_state.toggle_search_regex();
|
|
||||||
} else if let Some(flags) = &config_toml.flags {
|
|
||||||
if let Some(regex) = flags.regex {
|
|
||||||
if regex {
|
|
||||||
app.search_state.toggle_search_regex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up up tui and crossterm
|
// Set up up tui and crossterm
|
||||||
let mut stdout_val = stdout();
|
let mut stdout_val = stdout();
|
||||||
@ -299,37 +162,7 @@ fn main() -> error::Result<()> {
|
|||||||
|
|
||||||
// Set up input handling
|
// Set up input handling
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
{
|
create_input_thread(tx.clone());
|
||||||
let tx = tx.clone();
|
|
||||||
thread::spawn(move || loop {
|
|
||||||
if poll(Duration::from_millis(20)).is_ok() {
|
|
||||||
let mut mouse_timer = Instant::now();
|
|
||||||
let mut keyboard_timer = Instant::now();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if poll(Duration::from_millis(20)).is_ok() {
|
|
||||||
if let Ok(event) = read() {
|
|
||||||
if let CEvent::Key(key) = event {
|
|
||||||
if Instant::now().duration_since(keyboard_timer).as_millis() >= 20 {
|
|
||||||
if tx.send(Event::KeyInput(key)).is_err() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
keyboard_timer = Instant::now();
|
|
||||||
}
|
|
||||||
} else if let CEvent::Mouse(mouse) = event {
|
|
||||||
if Instant::now().duration_since(mouse_timer).as_millis() >= 20 {
|
|
||||||
if tx.send(Event::MouseInput(mouse)).is_err() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mouse_timer = Instant::now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleaning loop
|
// Cleaning loop
|
||||||
{
|
{
|
||||||
@ -343,33 +176,16 @@ fn main() -> error::Result<()> {
|
|||||||
}
|
}
|
||||||
// Event loop
|
// Event loop
|
||||||
let (rtx, rrx) = mpsc::channel();
|
let (rtx, rrx) = mpsc::channel();
|
||||||
{
|
create_event_thread(
|
||||||
let tx = tx;
|
tx,
|
||||||
let temp_type = app.temperature_type.clone();
|
rrx,
|
||||||
thread::spawn(move || {
|
use_current_cpu_total,
|
||||||
let tx = tx.clone();
|
update_rate_in_milliseconds as u64,
|
||||||
let mut data_state = data_harvester::DataState::default();
|
app.temperature_type.clone(),
|
||||||
data_state.init();
|
);
|
||||||
data_state.set_temperature_type(temp_type);
|
|
||||||
data_state.set_use_current_cpu_total(use_current_cpu_total);
|
|
||||||
loop {
|
|
||||||
if let Ok(message) = rrx.try_recv() {
|
|
||||||
match message {
|
|
||||||
ResetEvent::Reset => {
|
|
||||||
data_state.data.first_run_cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
futures::executor::block_on(data_state.update_data());
|
|
||||||
let event = Event::Update(Box::from(data_state.data.clone()));
|
|
||||||
tx.send(event).unwrap();
|
|
||||||
thread::sleep(Duration::from_millis(update_rate_in_milliseconds as u64));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut painter = canvas::Painter::default();
|
let mut painter = canvas::Painter::default();
|
||||||
if let Err(config_check) = generate_config_colours(&config_toml, &mut painter) {
|
if let Err(config_check) = generate_config_colours(&config, &mut painter) {
|
||||||
cleanup_terminal(&mut terminal)?;
|
cleanup_terminal(&mut terminal)?;
|
||||||
return Err(config_check);
|
return Err(config_check);
|
||||||
}
|
}
|
||||||
@ -409,10 +225,10 @@ fn main() -> error::Result<()> {
|
|||||||
match event.code {
|
match event.code {
|
||||||
KeyCode::Char('c') => break,
|
KeyCode::Char('c') => break,
|
||||||
KeyCode::Char('f') => app.enable_searching(),
|
KeyCode::Char('f') => app.enable_searching(),
|
||||||
KeyCode::Left => app.move_left(),
|
KeyCode::Left | KeyCode::Char('h') => app.move_left(),
|
||||||
KeyCode::Right => app.move_right(),
|
KeyCode::Right | KeyCode::Char('l') => app.move_right(),
|
||||||
KeyCode::Up => app.move_up(),
|
KeyCode::Up | KeyCode::Char('k') => app.move_up(),
|
||||||
KeyCode::Down => app.move_down(),
|
KeyCode::Down | KeyCode::Char('j') => app.move_down(),
|
||||||
KeyCode::Char('r') => {
|
KeyCode::Char('r') => {
|
||||||
if rtx.send(ResetEvent::Reset).is_ok() {
|
if rtx.send(ResetEvent::Reset).is_ok() {
|
||||||
app.reset();
|
app.reset();
|
||||||
@ -424,10 +240,19 @@ fn main() -> error::Result<()> {
|
|||||||
}
|
}
|
||||||
} else if let KeyModifiers::SHIFT = event.modifiers {
|
} else if let KeyModifiers::SHIFT = event.modifiers {
|
||||||
match event.code {
|
match event.code {
|
||||||
KeyCode::Left => app.move_left(),
|
KeyCode::Left | KeyCode::Char('h') | KeyCode::Char('H') => {
|
||||||
KeyCode::Right => app.move_right(),
|
app.move_left()
|
||||||
KeyCode::Up => app.move_up(),
|
}
|
||||||
KeyCode::Down => app.move_down(),
|
KeyCode::Right | KeyCode::Char('l') | KeyCode::Char('L') => {
|
||||||
|
app.move_right()
|
||||||
|
}
|
||||||
|
KeyCode::Up | KeyCode::Char('k') | KeyCode::Char('K') => {
|
||||||
|
app.move_up()
|
||||||
|
}
|
||||||
|
KeyCode::Down | KeyCode::Char('j') | KeyCode::Char('J') => {
|
||||||
|
app.move_down()
|
||||||
|
}
|
||||||
|
KeyCode::Char('/') | KeyCode::Char('?') => app.on_char_key('?'),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if let KeyModifiers::ALT = event.modifiers {
|
} else if let KeyModifiers::ALT = event.modifiers {
|
||||||
@ -525,6 +350,189 @@ fn main() -> error::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_logger() -> error::Result<()> {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
utils::logging::init_logger()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
|
||||||
|
let config_path = std::path::Path::new(flag_config_location.unwrap_or(
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
DEFAULT_WINDOWS_CONFIG_FILE_PATH
|
||||||
|
} else {
|
||||||
|
DEFAULT_UNIX_CONFIG_FILE_PATH
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
if let Ok(config_str) = std::fs::read_to_string(config_path) {
|
||||||
|
Ok(toml::from_str(config_str.as_str())?)
|
||||||
|
} else {
|
||||||
|
Ok(Config::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_update_rate_in_milliseconds(
|
||||||
|
update_rate: &Option<&str>, config: &Config,
|
||||||
|
) -> error::Result<u128> {
|
||||||
|
let update_rate_in_milliseconds = if let Some(update_rate) = update_rate {
|
||||||
|
update_rate.parse::<u128>()?
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(rate) = flags.rate {
|
||||||
|
rate as u128
|
||||||
|
} else {
|
||||||
|
constants::DEFAULT_REFRESH_RATE_IN_MILLISECONDS
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
constants::DEFAULT_REFRESH_RATE_IN_MILLISECONDS
|
||||||
|
};
|
||||||
|
|
||||||
|
if update_rate_in_milliseconds < 250 {
|
||||||
|
return Err(BottomError::InvalidArg(
|
||||||
|
"Please set your update rate to be greater than 250 milliseconds.".to_string(),
|
||||||
|
));
|
||||||
|
} else if update_rate_in_milliseconds > u128::from(std::u64::MAX) {
|
||||||
|
return Err(BottomError::InvalidArg(
|
||||||
|
"Please set your update rate to be less than unsigned INT_MAX.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(update_rate_in_milliseconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_temperature_option(
|
||||||
|
matches: &clap::ArgMatches<'static>, config: &Config,
|
||||||
|
) -> error::Result<data_harvester::temperature::TemperatureType> {
|
||||||
|
if matches.is_present("FAHRENHEIT") {
|
||||||
|
return Ok(data_harvester::temperature::TemperatureType::Fahrenheit);
|
||||||
|
} else if matches.is_present("KELVIN") {
|
||||||
|
return Ok(data_harvester::temperature::TemperatureType::Kelvin);
|
||||||
|
} else if matches.is_present("CELSIUS") {
|
||||||
|
return Ok(data_harvester::temperature::TemperatureType::Celsius);
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(temp_type) = &flags.temperature_type {
|
||||||
|
// Give lowest priority to config.
|
||||||
|
match temp_type.as_str() {
|
||||||
|
"fahrenheit" | "f" => {
|
||||||
|
return Ok(data_harvester::temperature::TemperatureType::Fahrenheit);
|
||||||
|
}
|
||||||
|
"kelvin" | "k" => {
|
||||||
|
return Ok(data_harvester::temperature::TemperatureType::Kelvin);
|
||||||
|
}
|
||||||
|
"celsius" | "c" => {
|
||||||
|
return Ok(data_harvester::temperature::TemperatureType::Celsius);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(BottomError::ConfigError(
|
||||||
|
"Invalid temperature type. Please have the value be of the form <kelvin|k|celsius|c|fahrenheit|f>".to_string()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(data_harvester::temperature::TemperatureType::Celsius)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_avg_cpu_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||||
|
if matches.is_present("AVG_CPU") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(avg_cpu) = flags.avg_cpu {
|
||||||
|
return avg_cpu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_use_dot_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||||
|
if matches.is_present("DOT_MARKER") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(dot_marker) = flags.dot_marker {
|
||||||
|
return dot_marker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_use_left_legend_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||||
|
if matches.is_present("LEFT_LEGEND") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(left_legend) = flags.left_legend {
|
||||||
|
return left_legend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_use_current_cpu_total_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||||
|
if matches.is_present("USE_CURR_USAGE") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(current_usage) = flags.current_usage {
|
||||||
|
return current_usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut app::App) {
|
||||||
|
if matches.is_present("GROUP_PROCESSES") {
|
||||||
|
app.toggle_grouping();
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(grouping) = flags.group_processes {
|
||||||
|
if grouping {
|
||||||
|
app.toggle_grouping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable_app_case_sensitive(
|
||||||
|
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut app::App,
|
||||||
|
) {
|
||||||
|
if matches.is_present("CASE_SENSITIVE") {
|
||||||
|
app.search_state.toggle_ignore_case();
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(case_sensitive) = flags.case_sensitive {
|
||||||
|
if case_sensitive {
|
||||||
|
app.search_state.toggle_ignore_case();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable_app_match_whole_word(
|
||||||
|
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut app::App,
|
||||||
|
) {
|
||||||
|
if matches.is_present("WHOLE_WORD") {
|
||||||
|
app.search_state.toggle_search_whole_word();
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(whole_word) = flags.whole_word {
|
||||||
|
if whole_word {
|
||||||
|
app.search_state.toggle_search_whole_word();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut app::App) {
|
||||||
|
if matches.is_present("REGEX_DEFAULT") {
|
||||||
|
app.search_state.toggle_search_regex();
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(regex) = flags.regex {
|
||||||
|
if regex {
|
||||||
|
app.search_state.toggle_search_regex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn try_drawing(
|
fn try_drawing(
|
||||||
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
|
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
|
||||||
app: &mut app::App, painter: &mut canvas::Painter,
|
app: &mut app::App, painter: &mut canvas::Painter,
|
||||||
@ -549,10 +557,8 @@ fn cleanup_terminal(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_config_colours(
|
fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> error::Result<()> {
|
||||||
config_toml: &Config, painter: &mut canvas::Painter,
|
if let Some(colours) = &config.colors {
|
||||||
) -> error::Result<()> {
|
|
||||||
if let Some(colours) = &config_toml.colors {
|
|
||||||
if let Some(border_color) = &colours.border_color {
|
if let Some(border_color) = &colours.border_color {
|
||||||
painter.colours.set_border_colour(border_color)?;
|
painter.colours.set_border_colour(border_color)?;
|
||||||
}
|
}
|
||||||
@ -720,3 +726,63 @@ fn sort_process_data(to_sort_vec: &mut Vec<ConvertedProcessData>, app: &app::App
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_input_thread(
|
||||||
|
tx: std::sync::mpsc::Sender<Event<crossterm::event::KeyEvent, crossterm::event::MouseEvent>>,
|
||||||
|
) {
|
||||||
|
thread::spawn(move || loop {
|
||||||
|
if poll(Duration::from_millis(20)).is_ok() {
|
||||||
|
let mut mouse_timer = Instant::now();
|
||||||
|
let mut keyboard_timer = Instant::now();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if poll(Duration::from_millis(20)).is_ok() {
|
||||||
|
if let Ok(event) = read() {
|
||||||
|
if let CEvent::Key(key) = event {
|
||||||
|
if Instant::now().duration_since(keyboard_timer).as_millis() >= 20 {
|
||||||
|
if tx.send(Event::KeyInput(key)).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
keyboard_timer = Instant::now();
|
||||||
|
}
|
||||||
|
} else if let CEvent::Mouse(mouse) = event {
|
||||||
|
if Instant::now().duration_since(mouse_timer).as_millis() >= 20 {
|
||||||
|
if tx.send(Event::MouseInput(mouse)).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mouse_timer = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_event_thread(
|
||||||
|
tx: std::sync::mpsc::Sender<Event<crossterm::event::KeyEvent, crossterm::event::MouseEvent>>,
|
||||||
|
rrx: std::sync::mpsc::Receiver<ResetEvent>, use_current_cpu_total: bool,
|
||||||
|
update_rate_in_milliseconds: u64, temp_type: data_harvester::temperature::TemperatureType,
|
||||||
|
) {
|
||||||
|
thread::spawn(move || {
|
||||||
|
let tx = tx.clone();
|
||||||
|
let mut data_state = data_harvester::DataState::default();
|
||||||
|
data_state.init();
|
||||||
|
data_state.set_temperature_type(temp_type);
|
||||||
|
data_state.set_use_current_cpu_total(use_current_cpu_total);
|
||||||
|
loop {
|
||||||
|
if let Ok(message) = rrx.try_recv() {
|
||||||
|
match message {
|
||||||
|
ResetEvent::Reset => {
|
||||||
|
data_state.data.first_run_cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
futures::executor::block_on(data_state.update_data());
|
||||||
|
let event = Event::Update(Box::from(data_state.data.clone()));
|
||||||
|
tx.send(event).unwrap();
|
||||||
|
thread::sleep(Duration::from_millis(update_rate_in_milliseconds));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user