mirror of
https://github.com/ClementTsang/bottom.git
synced 2026-09-19 09:05:49 +00:00
159 lines
5.0 KiB
Rust
159 lines
5.0 KiB
Rust
#![warn(rust_2018_idioms)]
|
|
#[allow(unused_imports)]
|
|
#[cfg(feature = "log")]
|
|
#[macro_use]
|
|
extern crate log;
|
|
|
|
use bottom::{
|
|
app::event::{EventResult, ReturnSignalResult},
|
|
canvas,
|
|
constants::*,
|
|
options::*,
|
|
*,
|
|
};
|
|
|
|
use std::{
|
|
boxed::Box,
|
|
io::stdout,
|
|
panic,
|
|
sync::{
|
|
atomic::{AtomicBool, Ordering},
|
|
mpsc, Arc, Condvar, Mutex,
|
|
},
|
|
thread,
|
|
time::Duration,
|
|
};
|
|
|
|
use anyhow::{Context, Result};
|
|
use crossterm::{
|
|
event::EnableMouseCapture,
|
|
execute,
|
|
terminal::{enable_raw_mode, EnterAlternateScreen},
|
|
};
|
|
use tui::{backend::CrosstermBackend, Terminal};
|
|
|
|
fn main() -> Result<()> {
|
|
let matches = clap::get_matches();
|
|
#[cfg(all(feature = "fern", debug_assertions))]
|
|
{
|
|
utils::logging::init_logger(log::LevelFilter::Debug, std::ffi::OsStr::new("debug.log"))?;
|
|
}
|
|
|
|
let config_path = read_config(matches.value_of("config_location"))
|
|
.context("Unable to access the given config file location.")?;
|
|
let mut config: Config = create_or_get_config(&config_path)
|
|
.context("Unable to properly parse or create the config file.")?;
|
|
|
|
// Create "app" struct, which will control most of the program and store settings/state
|
|
let mut app = build_app(&matches, &mut config)?;
|
|
|
|
// Create painter and set colours.
|
|
let mut painter = canvas::Painter::init(
|
|
app.app_config_fields.table_gap,
|
|
app.app_config_fields.use_basic_mode,
|
|
&config,
|
|
get_color_scheme(&matches, &config)?,
|
|
)?;
|
|
|
|
// Create termination mutex and cvar
|
|
#[allow(clippy::mutex_atomic)]
|
|
let thread_termination_lock = Arc::new(Mutex::new(false));
|
|
let thread_termination_cvar = Arc::new(Condvar::new());
|
|
|
|
// Set up input handling
|
|
let (sender, receiver) = mpsc::channel(); // FIXME: Make this bounded, prevents overloading.
|
|
let _input_thread = create_input_thread(sender.clone(), thread_termination_lock.clone());
|
|
|
|
// Cleaning loop
|
|
// TODO: Probably worth spinning this off into an async thread or something...
|
|
let _cleaning_thread = {
|
|
let lock = thread_termination_lock.clone();
|
|
let cvar = thread_termination_cvar.clone();
|
|
let cleaning_sender = sender.clone();
|
|
const OFFSET_WAIT_TIME: u64 = constants::STALE_MAX_MILLISECONDS + 60000;
|
|
thread::spawn(move || {
|
|
loop {
|
|
let result = cvar.wait_timeout(
|
|
lock.lock().unwrap(),
|
|
Duration::from_millis(OFFSET_WAIT_TIME),
|
|
);
|
|
if let Ok(result) = result {
|
|
if *(result.0) {
|
|
break;
|
|
}
|
|
}
|
|
if cleaning_sender.send(BottomEvent::Clean).is_err() {
|
|
// debug!("Failed to send cleaning sender...");
|
|
break;
|
|
}
|
|
}
|
|
})
|
|
};
|
|
|
|
// Event loop
|
|
// TODO: Add back collection sender
|
|
let (_collection_sender, collection_thread_ctrl_receiver) = mpsc::channel();
|
|
let _collection_thread = create_collection_thread(
|
|
sender,
|
|
collection_thread_ctrl_receiver,
|
|
thread_termination_lock.clone(),
|
|
thread_termination_cvar.clone(),
|
|
&app.app_config_fields,
|
|
app.filters.clone(),
|
|
app.used_widgets.clone(),
|
|
);
|
|
|
|
// 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.clear()?;
|
|
terminal.hide_cursor()?;
|
|
|
|
// Set panic hook
|
|
panic::set_hook(Box::new(|info| panic_hook(info)));
|
|
|
|
// Set termination hook
|
|
let is_terminated = Arc::new(AtomicBool::new(false));
|
|
let ist_clone = is_terminated.clone();
|
|
ctrlc::set_handler(move || {
|
|
ist_clone.store(true, Ordering::SeqCst);
|
|
})?;
|
|
|
|
while !is_terminated.load(Ordering::SeqCst) {
|
|
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
|
match app.handle_event(recv) {
|
|
EventResult::Quit => {
|
|
break;
|
|
}
|
|
EventResult::Redraw => {
|
|
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
|
}
|
|
EventResult::NoRedraw => {
|
|
continue;
|
|
}
|
|
EventResult::Signal(signal) => match app.handle_return_signal(signal) {
|
|
ReturnSignalResult::Quit => {
|
|
break;
|
|
}
|
|
ReturnSignalResult::Redraw => {
|
|
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
|
}
|
|
ReturnSignalResult::NoRedraw => {
|
|
continue;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
// I think doing it in this order is safe...
|
|
*thread_termination_lock.lock().unwrap() = true;
|
|
thread_termination_cvar.notify_all();
|
|
cleanup_terminal(&mut terminal)?;
|
|
|
|
Ok(())
|
|
}
|