2020-02-25 03:10:12 +01:00
#![ warn(rust_2018_idioms) ]
2020-04-27 01:47:39 +02:00
#[ allow(unused_imports) ]
2020-03-05 05:48:40 +01:00
#[ macro_use ]
2020-02-29 04:24:24 +01:00
extern crate log ;
2019-09-15 03:48:29 +02:00
2020-02-29 04:24:24 +01:00
use std ::{
2020-02-29 23:07:47 +01:00
boxed ::Box ,
io ::{ stdout , Write } ,
panic ::{ self , PanicInfo } ,
sync ::mpsc ,
thread ,
time ::{ Duration , Instant } ,
2020-02-29 04:24:24 +01:00
} ;
2020-02-07 05:58:33 +01:00
2020-03-13 06:07:24 +01:00
use clap ::* ;
2019-12-06 06:57:04 +01:00
use crossterm ::{
2020-02-29 23:07:47 +01:00
event ::{
2020-03-05 05:51:05 +01:00
poll , read , DisableMouseCapture , EnableMouseCapture , Event , KeyCode , KeyEvent ,
2020-02-29 23:07:47 +01:00
KeyModifiers , MouseEvent ,
2020-02-29 23:05:01 +01:00
} ,
2020-02-29 23:07:47 +01:00
execute ,
style ::Print ,
2020-03-05 05:45:59 +01:00
terminal ::{ disable_raw_mode , enable_raw_mode , EnterAlternateScreen , LeaveAlternateScreen } ,
2019-12-06 06:57:04 +01:00
} ;
2020-02-29 04:24:24 +01:00
use tui ::{ backend ::CrosstermBackend , Terminal } ;
2019-12-26 02:09:49 +01:00
2020-02-29 04:24:24 +01:00
use app ::{
2020-02-29 23:07:47 +01:00
data_harvester ::{ self , processes ::ProcessSorting } ,
2020-08-16 02:35:49 +02:00
layout_manager ::{ UsedWidgets , WidgetDirection } ,
2020-02-29 23:07:47 +01:00
App ,
2019-10-13 05:51:15 +02:00
} ;
2020-02-29 04:24:24 +01:00
use constants ::* ;
use data_conversion ::* ;
2020-02-29 23:05:01 +01:00
use options ::* ;
2020-03-08 06:35:01 +01:00
use utils ::error ;
2019-09-07 06:49:15 +02:00
2019-09-15 20:16:18 +02:00
pub mod app ;
2020-02-29 23:05:01 +01:00
2019-09-12 05:34:26 +02:00
mod utils {
2020-02-29 23:05:01 +01:00
pub mod error ;
pub mod gen_util ;
pub mod logging ;
2019-09-12 05:34:26 +02:00
}
2020-02-29 23:05:01 +01:00
2019-09-12 04:10:49 +02:00
mod canvas ;
2019-09-15 20:16:18 +02:00
mod constants ;
2019-12-13 08:42:36 +01:00
mod data_conversion ;
2019-09-15 20:16:18 +02:00
2020-02-29 23:05:01 +01:00
pub mod options ;
2020-03-05 05:45:59 +01:00
enum BottomEvent < I , J > {
2020-02-29 23:05:01 +01:00
KeyInput ( I ) ,
MouseInput ( J ) ,
Update ( Box < data_harvester ::Data > ) ,
Clean ,
2019-09-08 05:29:30 +02:00
}
2019-10-11 00:01:23 +02:00
enum ResetEvent {
2020-02-29 23:05:01 +01:00
Reset ,
2020-02-08 20:28:19 +01:00
}
2020-02-10 06:16:11 +01:00
fn get_matches ( ) -> clap ::ArgMatches < 'static > {
2020-02-29 23:05:01 +01:00
clap_app! ( app = >
2019-12-15 06:17:15 +01:00
( name : crate_name ! ( ) )
( version : crate_version ! ( ) )
( author : crate_authors ! ( ) )
( about : crate_description ! ( ) )
2020-05-17 04:38:19 +02:00
( @ arg HIDE_AVG_CPU : - a - - hide_avg_cpu " Hides the average CPU usage. " )
2019-12-15 06:17:15 +01:00
( @ arg DOT_MARKER : - m - - dot_marker " Use a dot marker instead of the default braille marker. " )
( @ group TEMPERATURE_TYPE = >
( @ arg KELVIN : - k - - kelvin " Sets the temperature type to Kelvin. " )
2020-02-05 02:05:07 +01:00
( @ arg FAHRENHEIT : - f - - fahrenheit " Sets the temperature type to Fahrenheit. " )
( @ arg CELSIUS : - c - - celsius " Sets the temperature type to Celsius. This is the default option. " )
2019-12-15 06:17:15 +01:00
)
( @ arg RATE_MILLIS : - r - - rate + takes_value " Sets a refresh rate in milliseconds; the minimum is 250ms, defaults to 1000ms. Smaller values may take more resources. " )
2019-12-28 07:21:49 +01:00
( @ arg LEFT_LEGEND : - l - - left_legend " Puts external chart legends on the left side rather than the default right side. " )
2020-01-01 04:24:54 +01:00
( @ arg USE_CURR_USAGE : - u - - current_usage " Within Linux, sets a process' CPU usage to be based on the total current CPU usage, rather than assuming 100% usage. " )
2020-03-05 05:47:53 +01:00
( @ arg CONFIG_LOCATION : - C - - config + takes_value " Sets the location of the config file. Expects a config file in the TOML format. If it doesn't exist, one is created. " )
2020-03-01 23:16:08 +01:00
( @ arg BASIC_MODE : - b - - basic " Hides graphs and uses a more basic look " )
2020-01-09 04:54:14 +01:00
( @ arg GROUP_PROCESSES : - g - - group " Groups processes with the same name together on launch. " )
2020-02-05 02:05:07 +01:00
( @ arg CASE_SENSITIVE : - S - - case_sensitive " Match case when searching by default. " )
2020-02-07 05:58:33 +01:00
( @ arg WHOLE_WORD : - W - - whole_word " Match whole word when searching by default. " )
2020-02-05 02:05:07 +01:00
( @ arg REGEX_DEFAULT : - R - - regex " Use regex in searching by default. " )
2020-03-09 02:56:30 +01:00
( @ arg DEFAULT_TIME_VALUE : - t - - default_time_value + takes_value " Default time value for graphs in milliseconds; minimum is 30s, defaults to 60s. " )
2020-03-09 03:19:57 +01:00
( @ arg TIME_DELTA : - d - - time_delta + takes_value " The amount changed upon zooming in/out in milliseconds; minimum is 1s, defaults to 15s. " )
( @ arg HIDE_TIME : - - hide_time " Completely hide the time scaling " )
2020-03-09 05:52:29 +01:00
( @ arg AUTOHIDE_TIME : - - autohide_time " Automatically hide the time scaling in graphs after being shown for a brief moment when zoomed in/out. If time is disabled via --hide_time then this will have no effect. " )
2020-04-02 02:31:43 +02:00
( @ arg DEFAULT_WIDGET_TYPE : - - default_widget_type + takes_value " The default widget type to select by default. " )
( @ arg DEFAULT_WIDGET_COUNT : - - default_widget_count + takes_value " Which number of the selected widget type to select, from left to right, top to bottom. Defaults to 1. " )
2020-04-19 23:45:32 +02:00
( @ arg USE_OLD_NETWORK_LEGEND : - - use_old_network_legend " Use the older (pre-0.4) network widget legend. " )
( @ arg HIDE_TABLE_GAP : - - hide_table_gap " Hides the spacing between the table headers and entries. " )
2020-04-27 19:48:56 +02:00
( @ arg BATTERY : - - battery " Shows the battery widget in default or basic mode. No effect on custom layouts. " )
2019-09-14 22:46:14 +02:00
)
2020-02-29 23:05:01 +01:00
. get_matches ( )
2020-02-10 06:16:11 +01:00
}
2019-09-14 22:46:14 +02:00
2020-02-10 06:16:11 +01:00
fn main ( ) -> error ::Result < ( ) > {
2020-05-02 05:53:29 +02:00
#[ cfg(debug_assertions) ]
{
utils ::logging ::init_logger ( ) ? ;
}
2020-02-29 23:05:01 +01:00
let matches = get_matches ( ) ;
let config : Config = create_config ( matches . value_of ( " CONFIG_LOCATION " ) ) ? ;
2020-04-02 02:31:43 +02:00
// Get widget layout separately
let ( widget_layout , default_widget_id ) = get_widget_layout ( & matches , & config ) ? ;
2020-02-29 23:05:01 +01:00
2020-04-02 02:31:43 +02:00
// Create "app" struct, which will control most of the program and store settings/state
let mut app = build_app ( & matches , & config , & widget_layout , default_widget_id ) ? ;
2020-02-29 23:05:01 +01:00
2020-04-23 21:33:31 +02:00
// Create painter and set colours.
let mut painter = canvas ::Painter ::init ( widget_layout , app . app_config_fields . table_gap ) ;
generate_config_colours ( & config , & mut painter ) ? ;
painter . colours . generate_remaining_cpu_colours ( ) ;
painter . complete_painter_init ( ) ;
2020-02-29 23:05:01 +01:00
// Set up input handling
2020-04-27 01:47:39 +02:00
let ( sender , receiver ) = mpsc ::channel ( ) ;
create_input_thread ( sender . clone ( ) ) ;
2020-02-29 23:05:01 +01:00
// Cleaning loop
{
2020-04-27 01:47:39 +02:00
let cleaning_sender = sender . clone ( ) ;
2020-02-29 23:05:01 +01:00
thread ::spawn ( move | | loop {
thread ::sleep ( Duration ::from_millis (
2020-03-13 06:07:24 +01:00
constants ::STALE_MAX_MILLISECONDS + 5000 ,
2020-02-29 23:05:01 +01:00
) ) ;
2020-04-27 01:47:39 +02:00
if cleaning_sender . send ( BottomEvent ::Clean ) . is_err ( ) {
break ;
}
2020-02-29 23:05:01 +01:00
} ) ;
}
2020-04-27 01:47:39 +02:00
2020-02-29 23:05:01 +01:00
// Event loop
2020-04-27 01:47:39 +02:00
let ( reset_sender , reset_receiver ) = mpsc ::channel ( ) ;
2020-02-29 23:05:01 +01:00
create_event_thread (
2020-04-27 02:08:02 +02:00
sender ,
2020-04-27 01:47:39 +02:00
reset_receiver ,
2020-03-13 06:07:24 +01:00
app . app_config_fields . use_current_cpu_total ,
app . app_config_fields . update_rate_in_milliseconds ,
2020-02-29 23:05:01 +01:00
app . app_config_fields . temperature_type . clone ( ) ,
2020-03-11 06:02:47 +01:00
app . app_config_fields . show_average_cpu ,
2020-04-05 00:29:32 +02:00
app . used_widgets . clone ( ) ,
2020-02-29 23:05:01 +01:00
) ;
2020-04-25 01:17:58 +02:00
// Set up up tui and crossterm
let mut stdout_val = stdout ( ) ;
execute! ( stdout_val , EnterAlternateScreen , EnableMouseCapture ) ? ;
enable_raw_mode ( ) ? ;
let mut terminal = Terminal ::new ( CrosstermBackend ::new ( stdout_val ) ) ? ;
terminal . hide_cursor ( ) ? ;
// Set panic hook
panic ::set_hook ( Box ::new ( | info | panic_hook ( info ) ) ) ;
2020-02-29 23:05:01 +01:00
loop {
2020-04-27 01:47:39 +02:00
if let Ok ( recv ) = receiver . recv_timeout ( Duration ::from_millis ( TICK_RATE_IN_MILLISECONDS ) ) {
2020-02-29 23:05:01 +01:00
match recv {
2020-03-05 05:45:59 +01:00
BottomEvent ::KeyInput ( event ) = > {
2020-04-27 01:47:39 +02:00
if handle_key_event_or_break ( event , & mut app , & reset_sender ) {
2020-02-29 23:05:01 +01:00
break ;
}
2020-03-09 00:47:10 +01:00
handle_force_redraws ( & mut app ) ;
}
BottomEvent ::MouseInput ( event ) = > {
handle_mouse_event ( event , & mut app ) ;
handle_force_redraws ( & mut app ) ;
2020-02-29 23:05:01 +01:00
}
2020-03-05 05:45:59 +01:00
BottomEvent ::Update ( data ) = > {
2020-02-29 23:05:01 +01:00
app . data_collection . eat_data ( & data ) ;
if ! app . is_frozen {
// Convert all data into tui-compliant components
// Network
2020-04-05 00:29:32 +02:00
if app . used_widgets . use_net {
2020-04-19 02:42:28 +02:00
let network_data = convert_network_data_points (
& app . data_collection ,
false ,
app . app_config_fields . use_basic_mode
| | app . app_config_fields . use_old_network_legend ,
) ;
2020-04-05 00:29:32 +02:00
app . canvas_data . network_data_rx = network_data . rx ;
app . canvas_data . network_data_tx = network_data . tx ;
app . canvas_data . rx_display = network_data . rx_display ;
app . canvas_data . tx_display = network_data . tx_display ;
2020-04-19 02:42:28 +02:00
if let Some ( total_rx_display ) = network_data . total_rx_display {
app . canvas_data . total_rx_display = total_rx_display ;
}
if let Some ( total_tx_display ) = network_data . total_tx_display {
app . canvas_data . total_tx_display = total_tx_display ;
}
2020-04-05 00:29:32 +02:00
}
2020-02-29 23:05:01 +01:00
// Disk
2020-04-05 00:29:32 +02:00
if app . used_widgets . use_disk {
app . canvas_data . disk_data = convert_disk_row ( & app . data_collection ) ;
}
2020-02-29 23:05:01 +01:00
// Temperatures
2020-04-05 00:29:32 +02:00
if app . used_widgets . use_temp {
app . canvas_data . temp_sensor_data = convert_temp_row ( & app ) ;
}
2020-04-02 02:31:43 +02:00
2020-02-29 23:05:01 +01:00
// Memory
2020-04-05 00:29:32 +02:00
if app . used_widgets . use_mem {
app . canvas_data . mem_data =
convert_mem_data_points ( & app . data_collection , false ) ;
app . canvas_data . swap_data =
convert_swap_data_points ( & app . data_collection , false ) ;
let memory_and_swap_labels = convert_mem_labels ( & app . data_collection ) ;
app . canvas_data . mem_label = memory_and_swap_labels . 0 ;
app . canvas_data . swap_label = memory_and_swap_labels . 1 ;
}
2020-02-29 23:05:01 +01:00
2020-04-05 00:29:32 +02:00
if app . used_widgets . use_cpu {
// CPU
app . canvas_data . cpu_data =
convert_cpu_data_points ( & app . data_collection , false ) ;
}
2020-02-29 23:05:01 +01:00
// Processes
2020-04-05 00:29:32 +02:00
if app . used_widgets . use_proc {
update_all_process_lists ( & mut app ) ;
}
2020-04-17 02:06:50 +02:00
// Battery
if app . used_widgets . use_battery {
app . canvas_data . battery_data =
convert_battery_harvest ( & app . data_collection ) ;
}
2020-02-29 23:05:01 +01:00
}
}
2020-03-05 05:45:59 +01:00
BottomEvent ::Clean = > {
2020-02-29 23:05:01 +01:00
app . data_collection
. clean_data ( constants ::STALE_MAX_MILLISECONDS ) ;
}
}
}
2020-04-27 01:47:39 +02:00
// TODO: [OPT] Should not draw if no change (ie: scroll max)
2020-02-29 23:05:01 +01:00
try_drawing ( & mut terminal , & mut app , & mut painter ) ? ;
}
cleanup_terminal ( & mut terminal ) ? ;
Ok ( ( ) )
2020-01-03 04:54:39 +01:00
}
2020-02-26 03:24:26 +01:00
fn handle_mouse_event ( event : MouseEvent , app : & mut App ) {
2020-02-29 23:05:01 +01:00
match event {
2020-03-08 21:17:28 +01:00
MouseEvent ::ScrollUp ( _x , _y , _modifiers ) = > app . handle_scroll_up ( ) ,
MouseEvent ::ScrollDown ( _x , _y , _modifiers ) = > app . handle_scroll_down ( ) ,
2020-02-29 23:05:01 +01:00
_ = > { }
} ;
2020-02-11 03:59:57 +01:00
}
fn handle_key_event_or_break (
2020-04-27 01:47:39 +02:00
event : KeyEvent , app : & mut App , reset_sender : & std ::sync ::mpsc ::Sender < ResetEvent > ,
2020-02-11 03:59:57 +01:00
) -> bool {
2020-02-29 23:05:01 +01:00
// debug!("KeyEvent: {:?}", event);
// TODO: [PASTE] Note that this does NOT support some emojis like flags. This is due to us
// catching PER CHARACTER right now WITH A forced throttle! This means multi-char will not work.
// We can solve this (when we do paste probably) while keeping the throttle (mainly meant for movement)
// by throttling after *bulk+singular* actions, not just singular ones.
if event . modifiers . is_empty ( ) {
// Required catch for searching - otherwise you couldn't search with q.
if event . code = = KeyCode ::Char ( 'q' ) & & ! app . is_in_search_widget ( ) {
return true ;
}
match event . code {
KeyCode ::End = > app . skip_to_last ( ) ,
KeyCode ::Home = > app . skip_to_first ( ) ,
KeyCode ::Up = > app . on_up_key ( ) ,
KeyCode ::Down = > app . on_down_key ( ) ,
KeyCode ::Left = > app . on_left_key ( ) ,
KeyCode ::Right = > app . on_right_key ( ) ,
KeyCode ::Char ( caught_char ) = > app . on_char_key ( caught_char ) ,
KeyCode ::Esc = > app . on_esc ( ) ,
KeyCode ::Enter = > app . on_enter ( ) ,
KeyCode ::Tab = > app . on_tab ( ) ,
KeyCode ::Backspace = > app . on_backspace ( ) ,
KeyCode ::Delete = > app . on_delete ( ) ,
2020-08-16 02:35:49 +02:00
KeyCode ::F ( 1 ) = > app . toggle_ignore_case ( ) ,
KeyCode ::F ( 2 ) = > app . toggle_search_whole_word ( ) ,
KeyCode ::F ( 3 ) = > app . toggle_search_regex ( ) ,
KeyCode ::F ( 6 ) = > app . toggle_sort ( ) ,
2020-02-29 23:05:01 +01:00
_ = > { }
}
} else {
// Otherwise, track the modifier as well...
if let KeyModifiers ::ALT = event . modifiers {
match event . code {
2020-05-02 05:53:29 +02:00
KeyCode ::Char ( 'c' ) | KeyCode ::Char ( 'C' ) = > app . toggle_ignore_case ( ) ,
KeyCode ::Char ( 'w' ) | KeyCode ::Char ( 'W' ) = > app . toggle_search_whole_word ( ) ,
KeyCode ::Char ( 'r' ) | KeyCode ::Char ( 'R' ) = > app . toggle_search_regex ( ) ,
2020-04-30 21:29:36 +02:00
KeyCode ::Char ( 'h' ) = > app . on_left_key ( ) ,
KeyCode ::Char ( 'l' ) = > app . on_right_key ( ) ,
2020-02-29 23:05:01 +01:00
_ = > { }
}
} else if let KeyModifiers ::CONTROL = event . modifiers {
if event . code = = KeyCode ::Char ( 'c' ) {
return true ;
}
match event . code {
KeyCode ::Char ( 'f' ) = > app . on_slash ( ) ,
2020-08-16 02:35:49 +02:00
KeyCode ::Left = > app . move_widget_selection ( & WidgetDirection ::Left ) ,
KeyCode ::Right = > app . move_widget_selection ( & WidgetDirection ::Right ) ,
KeyCode ::Up = > app . move_widget_selection ( & WidgetDirection ::Up ) ,
KeyCode ::Down = > app . move_widget_selection ( & WidgetDirection ::Down ) ,
2020-02-29 23:05:01 +01:00
KeyCode ::Char ( 'r' ) = > {
2020-04-27 01:47:39 +02:00
if reset_sender . send ( ResetEvent ::Reset ) . is_ok ( ) {
2020-02-29 23:05:01 +01:00
app . reset ( ) ;
}
}
KeyCode ::Char ( 'a' ) = > app . skip_cursor_beginning ( ) ,
KeyCode ::Char ( 'e' ) = > app . skip_cursor_end ( ) ,
2020-03-09 02:56:30 +01:00
KeyCode ::Char ( 'u' ) = > app . clear_search ( ) ,
2020-03-02 05:53:49 +01:00
// KeyCode::Char('j') => {}, // Move down
// KeyCode::Char('k') => {}, // Move up
// KeyCode::Char('h') => {}, // Move right
// KeyCode::Char('l') => {}, // Move left
2020-02-29 23:05:01 +01:00
// Can't do now, CTRL+BACKSPACE doesn't work and graphemes
// are hard to iter while truncating last (eloquently).
// KeyCode::Backspace => app.skip_word_backspace(),
_ = > { }
}
} else if let KeyModifiers ::SHIFT = event . modifiers {
match event . code {
2020-08-16 02:35:49 +02:00
KeyCode ::Left = > app . move_widget_selection ( & WidgetDirection ::Left ) ,
KeyCode ::Right = > app . move_widget_selection ( & WidgetDirection ::Right ) ,
KeyCode ::Up = > app . move_widget_selection ( & WidgetDirection ::Up ) ,
KeyCode ::Down = > app . move_widget_selection ( & WidgetDirection ::Down ) ,
2020-02-29 23:05:01 +01:00
KeyCode ::Char ( caught_char ) = > app . on_char_key ( caught_char ) ,
_ = > { }
}
}
}
false
2020-02-11 03:59:57 +01:00
}
2020-02-10 06:16:11 +01:00
fn create_config ( flag_config_location : Option < & str > ) -> error ::Result < Config > {
2020-03-05 05:48:40 +01:00
use std ::{ ffi ::OsString , fs } ;
let config_path = if let Some ( conf_loc ) = flag_config_location {
2020-08-12 08:15:57 +02:00
Some ( OsString ::from ( conf_loc ) )
2020-03-05 05:48:40 +01:00
} else if cfg! ( target_os = " windows " ) {
if let Some ( home_path ) = dirs ::config_dir ( ) {
let mut path = home_path ;
2020-08-12 08:15:57 +02:00
path . push ( DEFAULT_CONFIG_FILE_PATH ) ;
Some ( path . into_os_string ( ) )
2020-03-05 05:48:40 +01:00
} else {
2020-08-12 08:15:57 +02:00
None
2020-03-05 05:48:40 +01:00
}
} else if let Some ( home_path ) = dirs ::home_dir ( ) {
let mut path = home_path ;
2020-08-12 08:15:57 +02:00
path . push ( " .config/ " ) ;
path . push ( DEFAULT_CONFIG_FILE_PATH ) ;
if path . exists ( ) {
// If it already exists, use the old one.
Some ( path . into_os_string ( ) )
} else {
// If it does not, use the new one!
if let Some ( config_path ) = dirs ::config_dir ( ) {
let mut path = config_path ;
path . push ( DEFAULT_CONFIG_FILE_PATH ) ;
Some ( path . into_os_string ( ) )
} else {
None
}
}
2020-03-05 05:48:40 +01:00
} else {
2020-08-12 08:15:57 +02:00
None
2020-03-05 05:48:40 +01:00
} ;
2020-08-12 08:15:57 +02:00
if let Some ( config_path ) = config_path {
let path = std ::path ::Path ::new ( & config_path ) ;
2020-03-05 05:48:40 +01:00
2020-08-12 08:15:57 +02:00
if let Ok ( config_string ) = fs ::read_to_string ( path ) {
Ok ( toml ::from_str ( config_string . as_str ( ) ) ? )
} else {
if let Some ( parent_path ) = path . parent ( ) {
fs ::create_dir_all ( parent_path ) ? ;
}
fs ::File ::create ( path ) ? . write_all ( DEFAULT_CONFIG_CONTENT . as_bytes ( ) ) ? ;
Ok ( toml ::from_str ( DEFAULT_CONFIG_CONTENT ) ? )
2020-03-05 05:48:40 +01:00
}
2020-08-12 08:15:57 +02:00
} else {
// Don't write otherwise...
2020-03-05 05:48:40 +01:00
Ok ( toml ::from_str ( DEFAULT_CONFIG_CONTENT ) ? )
}
2020-02-10 06:16:11 +01:00
}
2020-02-29 23:05:01 +01:00
2020-02-05 04:44:49 +01:00
fn try_drawing (
2020-02-29 23:05:01 +01:00
terminal : & mut tui ::terminal ::Terminal < tui ::backend ::CrosstermBackend < std ::io ::Stdout > > ,
app : & mut App , painter : & mut canvas ::Painter ,
2020-02-05 04:44:49 +01:00
) -> error ::Result < ( ) > {
2020-02-29 23:05:01 +01:00
if let Err ( err ) = painter . draw_data ( terminal , app ) {
cleanup_terminal ( terminal ) ? ;
return Err ( err ) ;
}
2020-02-05 04:44:49 +01:00
2020-02-29 23:05:01 +01:00
Ok ( ( ) )
2020-02-05 04:44:49 +01:00
}
2020-02-02 23:09:42 +01:00
fn cleanup_terminal (
2020-02-29 23:05:01 +01:00
terminal : & mut tui ::terminal ::Terminal < tui ::backend ::CrosstermBackend < std ::io ::Stdout > > ,
2020-01-08 05:40:53 +01:00
) -> error ::Result < ( ) > {
2020-02-29 23:05:01 +01:00
disable_raw_mode ( ) ? ;
execute! (
2020-02-29 23:07:47 +01:00
terminal . backend_mut ( ) ,
DisableMouseCapture ,
LeaveAlternateScreen
) ? ;
2020-02-29 23:05:01 +01:00
terminal . show_cursor ( ) ? ;
2020-01-03 04:54:39 +01:00
2020-02-29 23:05:01 +01:00
Ok ( ( ) )
2019-09-10 00:34:13 +02:00
}
2020-02-02 05:49:44 +01:00
2020-02-10 06:16:11 +01:00
fn generate_config_colours ( config : & Config , painter : & mut canvas ::Painter ) -> error ::Result < ( ) > {
2020-02-29 23:05:01 +01:00
if let Some ( colours ) = & config . colors {
if let Some ( border_color ) = & colours . border_color {
painter . colours . set_border_colour ( border_color ) ? ;
}
if let Some ( highlighted_border_color ) = & colours . highlighted_border_color {
painter
. colours
. set_highlighted_border_colour ( highlighted_border_color ) ? ;
}
if let Some ( text_color ) = & colours . text_color {
painter . colours . set_text_colour ( text_color ) ? ;
}
if let Some ( avg_cpu_color ) = & colours . avg_cpu_color {
painter . colours . set_avg_cpu_colour ( avg_cpu_color ) ? ;
}
2020-05-17 04:38:19 +02:00
if let Some ( all_cpu_color ) = & colours . all_cpu_color {
painter . colours . set_all_cpu_colour ( all_cpu_color ) ? ;
}
2020-02-29 23:05:01 +01:00
if let Some ( cpu_core_colors ) = & colours . cpu_core_colors {
painter . colours . set_cpu_colours ( cpu_core_colors ) ? ;
}
if let Some ( ram_color ) = & colours . ram_color {
painter . colours . set_ram_colour ( ram_color ) ? ;
}
if let Some ( swap_color ) = & colours . swap_color {
painter . colours . set_swap_colour ( swap_color ) ? ;
}
if let Some ( rx_color ) = & colours . rx_color {
painter . colours . set_rx_colour ( rx_color ) ? ;
}
if let Some ( tx_color ) = & colours . tx_color {
painter . colours . set_tx_colour ( tx_color ) ? ;
}
2020-04-19 02:42:28 +02:00
// if let Some(rx_total_color) = &colours.rx_total_color {
// painter.colours.set_rx_total_colour(rx_total_color)?;
// }
2020-02-29 23:05:01 +01:00
2020-04-19 02:42:28 +02:00
// if let Some(tx_total_color) = &colours.tx_total_color {
// painter.colours.set_tx_total_colour(tx_total_color)?;
// }
2020-02-29 23:05:01 +01:00
if let Some ( table_header_color ) = & colours . table_header_color {
painter
. colours
. set_table_header_colour ( table_header_color ) ? ;
}
if let Some ( scroll_entry_text_color ) = & colours . selected_text_color {
painter
. colours
. set_scroll_entry_text_color ( scroll_entry_text_color ) ? ;
}
if let Some ( scroll_entry_bg_color ) = & colours . selected_bg_color {
painter
. colours
. set_scroll_entry_bg_color ( scroll_entry_bg_color ) ? ;
}
if let Some ( widget_title_color ) = & colours . widget_title_color {
painter
. colours
. set_widget_title_colour ( widget_title_color ) ? ;
}
if let Some ( graph_color ) = & colours . graph_color {
painter . colours . set_graph_colour ( graph_color ) ? ;
}
2020-04-17 02:06:50 +02:00
if let Some ( battery_colors ) = & colours . battery_colors {
painter . colours . set_battery_colours ( battery_colors ) ? ;
}
2020-02-29 23:05:01 +01:00
}
Ok ( ( ) )
2020-02-08 20:28:19 +01:00
}
2020-02-02 23:09:42 +01:00
/// Based on https://github.com/Rigellute/spotify-tui/blob/master/src/main.rs
fn panic_hook ( panic_info : & PanicInfo < '_ > ) {
2020-02-29 23:05:01 +01:00
let mut stdout = stdout ( ) ;
2020-02-02 23:09:42 +01:00
2020-02-29 23:05:01 +01:00
let msg = match panic_info . payload ( ) . downcast_ref ::< & 'static str > ( ) {
Some ( s ) = > * s ,
None = > match panic_info . payload ( ) . downcast_ref ::< String > ( ) {
Some ( s ) = > & s [ .. ] ,
None = > " Box<Any> " ,
} ,
} ;
2020-02-19 05:33:15 +01:00
2020-02-29 23:05:01 +01:00
let stacktrace : String = format! ( " {:?} " , backtrace ::Backtrace ::new ( ) ) ;
2020-02-02 23:09:42 +01:00
2020-02-29 23:05:01 +01:00
disable_raw_mode ( ) . unwrap ( ) ;
execute! ( stdout , LeaveAlternateScreen , DisableMouseCapture ) . unwrap ( ) ;
2020-02-19 05:33:15 +01:00
2020-02-29 23:05:01 +01:00
// Print stack trace. Must be done after!
execute! (
2020-02-29 23:07:47 +01:00
stdout ,
Print ( format! (
" thread '<unnamed>' panicked at '{}', {} \n \r {} " ,
msg ,
panic_info . location ( ) . unwrap ( ) ,
stacktrace
) ) ,
)
. unwrap ( ) ;
2020-02-02 23:09:42 +01:00
}
2020-03-09 00:47:10 +01:00
fn handle_force_redraws ( app : & mut App ) {
2020-04-02 02:31:43 +02:00
// Currently we use an Option... because we might want to future-proof this
// if we eventually get widget-specific redrawing!
if app . proc_state . force_update_all {
update_all_process_lists ( app ) ;
app . proc_state . force_update_all = false ;
} else if let Some ( widget_id ) = app . proc_state . force_update {
update_final_process_list ( app , widget_id ) ;
app . proc_state . force_update = None ;
2020-03-09 00:47:10 +01:00
}
2020-04-02 02:31:43 +02:00
if app . cpu_state . force_update . is_some ( ) {
app . canvas_data . cpu_data = convert_cpu_data_points ( & app . data_collection , app . is_frozen ) ;
app . cpu_state . force_update = None ;
2020-03-09 00:47:10 +01:00
}
2020-04-02 02:31:43 +02:00
if app . mem_state . force_update . is_some ( ) {
app . canvas_data . mem_data = convert_mem_data_points ( & app . data_collection , app . is_frozen ) ;
app . canvas_data . swap_data = convert_swap_data_points ( & app . data_collection , app . is_frozen ) ;
app . mem_state . force_update = None ;
2020-03-09 00:47:10 +01:00
}
2020-04-02 02:31:43 +02:00
if app . net_state . force_update . is_some ( ) {
let ( rx , tx ) = get_rx_tx_data_points ( & app . data_collection , app . is_frozen ) ;
2020-03-09 00:47:10 +01:00
app . canvas_data . network_data_rx = rx ;
app . canvas_data . network_data_tx = tx ;
2020-04-02 02:31:43 +02:00
app . net_state . force_update = None ;
2020-03-09 00:47:10 +01:00
}
}
2020-04-02 02:31:43 +02:00
fn update_all_process_lists ( app : & mut App ) {
let widget_ids = app
. proc_state
. widget_states
. keys ( )
. cloned ( )
. collect ::< Vec < _ > > ( ) ;
widget_ids . into_iter ( ) . for_each ( | widget_id | {
update_final_process_list ( app , widget_id ) ;
} ) ;
}
fn update_final_process_list ( app : & mut App , widget_id : u64 ) {
let is_invalid_or_blank = match app . proc_state . widget_states . get ( & widget_id ) {
Some ( process_state ) = > process_state
. process_search_state
. search_state
. is_invalid_or_blank_search ( ) ,
None = > false ,
} ;
2020-08-07 10:29:20 +02:00
let is_grouped = app . is_grouped ( widget_id ) ;
if let Some ( proc_widget_state ) = app . proc_state . get_mut_widget_state ( widget_id ) {
app . canvas_data . process_data = convert_process_data (
& app . data_collection ,
if is_grouped {
ProcessGroupingType ::Grouped
} else {
ProcessGroupingType ::Ungrouped
} ,
2020-08-16 02:35:49 +02:00
if proc_widget_state . is_using_command {
2020-08-07 10:29:20 +02:00
ProcessNamingType ::Path
} else {
ProcessNamingType ::Name
} ,
) ;
}
2020-04-02 02:31:43 +02:00
2020-05-02 05:53:29 +02:00
let process_filter = app . get_process_filter ( widget_id ) ;
2020-08-07 10:29:20 +02:00
let filtered_process_data : Vec < ConvertedProcessData > = app
. canvas_data
. process_data
. iter ( )
. filter ( | process | {
if ! is_invalid_or_blank {
if let Some ( process_filter ) = process_filter {
process_filter . check ( & process )
2020-05-02 05:53:29 +02:00
} else {
true
2020-02-29 23:05:01 +01:00
}
2020-08-07 10:29:20 +02:00
} else {
true
}
} )
. cloned ( )
. collect ::< Vec < _ > > ( ) ;
2020-02-29 23:05:01 +01:00
2020-04-02 02:31:43 +02:00
// Quick fix for tab updating the table headers
2020-08-07 10:29:20 +02:00
if let Some ( proc_widget_state ) = app . proc_state . get_mut_widget_state ( widget_id ) {
2020-08-16 02:35:49 +02:00
if let data_harvester ::processes ::ProcessSorting ::Pid =
2020-04-02 02:31:43 +02:00
proc_widget_state . process_sorting_type
{
if proc_widget_state . is_grouped {
proc_widget_state . process_sorting_type =
2020-08-16 02:35:49 +02:00
data_harvester ::processes ::ProcessSorting ::CpuPercent ; // Go back to default, negate PID for group
2020-04-02 02:31:43 +02:00
proc_widget_state . process_sorting_reverse = true ;
}
}
let mut resulting_processes = filtered_process_data ;
sort_process_data ( & mut resulting_processes , proc_widget_state ) ;
2020-05-19 05:34:50 +02:00
if proc_widget_state . scroll_state . current_scroll_position > = resulting_processes . len ( ) {
2020-04-02 02:31:43 +02:00
proc_widget_state . scroll_state . current_scroll_position =
2020-05-19 05:34:50 +02:00
resulting_processes . len ( ) . saturating_sub ( 1 ) ;
2020-04-02 02:31:43 +02:00
proc_widget_state . scroll_state . previous_scroll_position = 0 ;
2020-08-16 02:35:49 +02:00
proc_widget_state . scroll_state . scroll_direction = app ::ScrollDirection ::Down ;
2020-04-02 02:31:43 +02:00
}
app . canvas_data
. finalized_process_data_map
. insert ( widget_id , resulting_processes ) ;
}
2020-02-02 05:49:44 +01:00
}
2020-04-02 02:31:43 +02:00
fn sort_process_data (
to_sort_vec : & mut Vec < ConvertedProcessData > , proc_widget_state : & app ::ProcWidgetState ,
) {
2020-04-08 03:42:57 +02:00
to_sort_vec . sort_by ( | a , b | {
utils ::gen_util ::get_ordering ( & a . name . to_lowercase ( ) , & b . name . to_lowercase ( ) , false )
} ) ;
2020-02-29 23:05:01 +01:00
2020-04-02 02:31:43 +02:00
match proc_widget_state . process_sorting_type {
2020-08-16 02:35:49 +02:00
ProcessSorting ::CpuPercent = > {
2020-02-29 23:05:01 +01:00
to_sort_vec . sort_by ( | a , b | {
2020-04-02 02:31:43 +02:00
utils ::gen_util ::get_ordering (
a . cpu_usage ,
b . cpu_usage ,
proc_widget_state . process_sorting_reverse ,
)
2020-02-29 23:05:01 +01:00
} ) ;
}
2020-08-16 02:35:49 +02:00
ProcessSorting ::Mem = > {
// TODO: Do when I do mem values in processes
}
ProcessSorting ::MemPercent = > {
2020-02-29 23:05:01 +01:00
to_sort_vec . sort_by ( | a , b | {
2020-04-02 02:31:43 +02:00
utils ::gen_util ::get_ordering (
a . mem_usage ,
b . mem_usage ,
proc_widget_state . process_sorting_reverse ,
)
2020-02-29 23:05:01 +01:00
} ) ;
}
2020-08-16 02:35:49 +02:00
ProcessSorting ::ProcessName | ProcessSorting ::Command = > {
// Don't repeat if false... it sorts by name by default anyways.
2020-04-02 02:31:43 +02:00
if proc_widget_state . process_sorting_reverse {
to_sort_vec . sort_by ( | a , b | {
utils ::gen_util ::get_ordering (
2020-04-08 03:42:57 +02:00
& a . name . to_lowercase ( ) ,
& b . name . to_lowercase ( ) ,
2020-04-02 02:31:43 +02:00
proc_widget_state . process_sorting_reverse ,
)
} )
}
}
2020-08-16 02:35:49 +02:00
ProcessSorting ::Pid = > {
2020-04-02 02:31:43 +02:00
if ! proc_widget_state . is_grouped {
2020-02-29 23:05:01 +01:00
to_sort_vec . sort_by ( | a , b | {
2020-04-02 02:31:43 +02:00
utils ::gen_util ::get_ordering (
a . pid ,
b . pid ,
proc_widget_state . process_sorting_reverse ,
)
2020-02-29 23:05:01 +01:00
} ) ;
}
}
2020-08-16 02:35:49 +02:00
ProcessSorting ::ReadPerSecond = > {
to_sort_vec . sort_by ( | a , b | {
utils ::gen_util ::get_ordering (
a . rps_f64 ,
b . rps_f64 ,
proc_widget_state . process_sorting_reverse ,
)
} ) ;
}
ProcessSorting ::WritePerSecond = > {
to_sort_vec . sort_by ( | a , b | {
utils ::gen_util ::get_ordering (
a . wps_f64 ,
b . wps_f64 ,
proc_widget_state . process_sorting_reverse ,
)
} ) ;
}
ProcessSorting ::TotalRead = > {
to_sort_vec . sort_by ( | a , b | {
utils ::gen_util ::get_ordering (
a . tr_f64 ,
b . tr_f64 ,
proc_widget_state . process_sorting_reverse ,
)
} ) ;
}
ProcessSorting ::TotalWrite = > {
to_sort_vec . sort_by ( | a , b | {
utils ::gen_util ::get_ordering (
a . tw_f64 ,
b . tw_f64 ,
proc_widget_state . process_sorting_reverse ,
)
} ) ;
}
ProcessSorting ::State = > to_sort_vec . sort_by ( | a , b | {
utils ::gen_util ::get_ordering (
& a . process_state . to_lowercase ( ) ,
& b . process_state . to_lowercase ( ) ,
proc_widget_state . process_sorting_reverse ,
)
} ) ,
2020-02-29 23:05:01 +01:00
}
2020-02-02 05:49:44 +01:00
}
2020-02-10 06:16:11 +01:00
fn create_input_thread (
2020-04-27 01:47:39 +02:00
sender : std ::sync ::mpsc ::Sender <
2020-03-05 05:45:59 +01:00
BottomEvent < crossterm ::event ::KeyEvent , crossterm ::event ::MouseEvent > ,
> ,
2020-02-10 06:16:11 +01:00
) {
2020-04-27 01:47:39 +02:00
thread ::spawn ( move | | {
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 Event ::Key ( key ) = event {
if Instant ::now ( ) . duration_since ( keyboard_timer ) . as_millis ( ) > = 20 {
if sender . send ( BottomEvent ::KeyInput ( key ) ) . is_err ( ) {
return ;
2020-02-29 23:05:01 +01:00
}
2020-04-27 01:47:39 +02:00
keyboard_timer = Instant ::now ( ) ;
}
} else if let Event ::Mouse ( mouse ) = event {
if Instant ::now ( ) . duration_since ( mouse_timer ) . as_millis ( ) > = 20 {
if sender . send ( BottomEvent ::MouseInput ( mouse ) ) . is_err ( ) {
return ;
2020-02-29 23:05:01 +01:00
}
2020-04-27 01:47:39 +02:00
mouse_timer = Instant ::now ( ) ;
2020-02-29 23:05:01 +01:00
}
}
}
}
}
} ) ;
2020-02-10 06:16:11 +01:00
}
fn create_event_thread (
2020-04-27 01:47:39 +02:00
sender : std ::sync ::mpsc ::Sender <
2020-03-05 05:45:59 +01:00
BottomEvent < crossterm ::event ::KeyEvent , crossterm ::event ::MouseEvent > ,
> ,
2020-04-27 01:47:39 +02:00
reset_receiver : std ::sync ::mpsc ::Receiver < ResetEvent > , use_current_cpu_total : bool ,
2020-02-29 23:05:01 +01:00
update_rate_in_milliseconds : u64 , temp_type : data_harvester ::temperature ::TemperatureType ,
2020-04-05 00:29:32 +02:00
show_average_cpu : bool , used_widget_set : UsedWidgets ,
2020-02-10 06:16:11 +01:00
) {
2020-02-29 23:05:01 +01:00
thread ::spawn ( move | | {
2020-04-05 00:29:32 +02:00
let mut data_state = data_harvester ::DataCollector ::default ( ) ;
data_state . set_collected_data ( used_widget_set ) ;
2020-02-29 23:05:01 +01:00
data_state . set_temperature_type ( temp_type ) ;
data_state . set_use_current_cpu_total ( use_current_cpu_total ) ;
2020-03-11 06:02:47 +01:00
data_state . set_show_average_cpu ( show_average_cpu ) ;
2020-04-05 00:29:32 +02:00
data_state . init ( ) ;
2020-02-29 23:05:01 +01:00
loop {
2020-04-27 01:47:39 +02:00
if let Ok ( message ) = reset_receiver . try_recv ( ) {
2020-02-29 23:05:01 +01:00
match message {
ResetEvent ::Reset = > {
data_state . data . first_run_cleanup ( ) ;
}
}
}
futures ::executor ::block_on ( data_state . update_data ( ) ) ;
2020-03-05 05:45:59 +01:00
let event = BottomEvent ::Update ( Box ::from ( data_state . data ) ) ;
2020-02-29 23:05:01 +01:00
data_state . data = data_harvester ::Data ::default ( ) ;
2020-04-27 01:47:39 +02:00
if sender . send ( event ) . is_err ( ) {
break ;
}
2020-02-29 23:05:01 +01:00
thread ::sleep ( Duration ::from_millis ( update_rate_in_milliseconds ) ) ;
}
} ) ;
2020-02-10 06:16:11 +01:00
}