From 846e0641d062146bfded05d36b0db9706fda1fcc Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:52:03 +0000 Subject: [PATCH 01/21] wip: mouse capture errors --- Cargo.lock | 4 +-- src/main.rs | 40 ++++++++++++++++-------------- src/ui/mod.rs | 67 +++++++++++++++++++++++++++++++++------------------ 3 files changed, 67 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87f1446..70f8d6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,9 +370,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" dependencies = [ "bytes", "fnv", diff --git a/src/main.rs b/src/main.rs index 660ae64..27611a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,20 @@ #![forbid(unsafe_code)] -#![warn( - clippy::nursery, - clippy::pedantic, - clippy::expect_used, - clippy::todo, - clippy::unused_async, - clippy::unwrap_used -)] -// Warning - These are indeed pedantic -#![allow( - clippy::module_name_repetitions, - clippy::doc_markdown, - clippy::similar_names -)] +// #![warn( +// clippy::nursery, +// clippy::pedantic, +// clippy::expect_used, +// clippy::todo, +// clippy::unused_async, +// clippy::unwrap_used +// )] +// // Warning - These are indeed pedantic +// #![allow( +// clippy::module_name_repetitions, +// clippy::doc_markdown, +// clippy::similar_names +// )] // Only allow when debugging -// #![allow(unused)] +#![allow(unused)] use app_data::AppData; use app_error::AppError; @@ -23,7 +23,10 @@ use docker_data::DockerData; use input_handler::InputMessages; use parking_lot::Mutex; use parse_args::CliArgs; -use std::{sync::{atomic::AtomicBool, Arc}, io::Write}; +use std::{ + io::Write, + sync::{atomic::AtomicBool, Arc}, +}; use tokio::sync::mpsc::{Receiver, Sender}; use tracing::{info, Level}; @@ -146,6 +149,7 @@ async fn main() { .await; } } - // Clear screen - std::io::stdout().flush().unwrap_or(()); + // Clear screen + // std::io::stdout().lock().flush().unwrap_or(()); + // std::io::stdout().lock().write(b"").unwrap_or_default(); } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d4720ed..54c92a3 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -6,7 +6,7 @@ use crossterm::{ }; use parking_lot::Mutex; use std::{ - io, + io::{self, Write}, sync::{atomic::Ordering, Arc}, }; use std::{sync::atomic::AtomicBool, time::Instant}; @@ -38,11 +38,12 @@ pub async fn create_ui( ) -> Result<()> { enable_raw_mode()?; let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + // EnableMouseCapture + execute!(stdout, EnableMouseCapture, EnterAlternateScreen)?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; - let res = run_app( + run_app( app_data, docker_sx, gui_state, @@ -50,7 +51,8 @@ pub async fn create_ui( sender, &mut terminal, ) - .await; + .await + .unwrap_or(()); disable_raw_mode()?; execute!( terminal.backend_mut(), @@ -59,8 +61,39 @@ pub async fn create_ui( )?; terminal.show_cursor()?; - if let Err(err) = res { - println!("error: {err}"); + // if let Err(err) = res { + // println!("error: {err}"); + // } + std::io::stdout().flush().unwrap_or(()); + Ok(()) +} + +/// Display error message for 5 seconds, with countdown +fn err_loop( + now: &mut Instant, + terminal: &mut Terminal, +) -> Result<(), AppError> { + let mut seconds = 5; + loop { + if seconds < 1 { + // terminal.clear().unwrap_or(()); + break; + } + + if now.elapsed() >= std::time::Duration::from_secs(1) { + seconds -= 1; + *now = Instant::now(); + } + + terminal + .draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds))) + .unwrap(); + // { + // return Err(AppError::Terminal); + // } + // terminal + // .draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds))) + // .unwrap(); } Ok(()) } @@ -79,23 +112,9 @@ async fn run_app( let input_poll_rate = std::time::Duration::from_millis(75); let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]); let mut now = Instant::now(); - if status_dockerconnect { - let mut seconds = 5; - loop { - if seconds < 1 { - break; - } - if now.elapsed() >= std::time::Duration::from_secs(1) { - seconds -= 1; - now = Instant::now(); - } - if terminal - .draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds))) - .is_err() - { - return Err(AppError::Terminal); - } - } + + if !status_dockerconnect { + err_loop(&mut now, terminal).unwrap_or(()); } else { while is_running.load(Ordering::SeqCst) { if crossterm::event::poll(input_poll_rate).unwrap_or(false) { @@ -126,7 +145,7 @@ async fn run_app( } } } - terminal.clear().unwrap_or(()); + Ok(()) } From 31bfaa7a957d9e87874de2e9c8c72c02f01ceb79 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Tue, 28 Feb 2023 04:10:31 +0000 Subject: [PATCH 02/21] wip: fix for mouse capture bug --- src/docker_data/mod.rs | 6 +++ src/input_handler/mod.rs | 5 ++ src/main.rs | 8 +++ src/ui/mod.rs | 114 ++++++++++++++++++++------------------- 4 files changed, 78 insertions(+), 55 deletions(-) diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 5d998e6..3d71a1a 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -3,6 +3,7 @@ use bollard::{ service::ContainerSummary, Docker, }; +use crossterm::{event::DisableMouseCapture, execute}; use futures_util::StreamExt; use parking_lot::Mutex; use std::{ @@ -407,6 +408,11 @@ impl DockerData { .values() .into_iter() .for_each(tokio::task::JoinHandle::abort); + // This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout + // execute!(std::io::stdout(), DisableMouseCapture).unwrap_or(()); + std::thread::spawn(||{ + execute!(std::io::stdout(), DisableMouseCapture).unwrap_or(()); + }); self.is_running.store(false, Ordering::SeqCst); } } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 7e1f649..0a01fcd 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -134,6 +134,11 @@ impl InputHandler { .lock() .status_contains(&[Status::Error, Status::Init]); if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() { + // This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout + + std::thread::spawn(||{ + execute!(std::io::stdout(), DisableMouseCapture).unwrap_or(()); + }); self.is_running.store(false, Ordering::SeqCst); } } diff --git a/src/main.rs b/src/main.rs index 27611a3..59deece 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,6 +149,14 @@ async fn main() { .await; } } + + // let mut child = std::process::Command::new("tput").arg("reset").spawn().unwrap_or_else(|e| panic!("Could not run tput: {}", e)); + // let result = child.wait().unwrap_or_else(|e| panic!("Could not wait for tput process: {}", e)); + + // if ! result.success() { + // panic!("tput failed with error code {}", result.code().unwrap()); + // } + // Clear screen // std::io::stdout().lock().flush().unwrap_or(()); // std::io::stdout().lock().write(b"").unwrap_or_default(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 54c92a3..65a2db0 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -7,6 +7,7 @@ use crossterm::{ use parking_lot::Mutex; use std::{ io::{self, Write}, + process::Stdio, sync::{atomic::Ordering, Arc}, }; use std::{sync::atomic::AtomicBool, time::Instant}; @@ -59,45 +60,81 @@ pub async fn create_ui( LeaveAlternateScreen, DisableMouseCapture )?; - terminal.show_cursor()?; - // if let Err(err) = res { - // println!("error: {err}"); - // } - std::io::stdout().flush().unwrap_or(()); + terminal.show_cursor()?; Ok(()) } -/// Display error message for 5 seconds, with countdown +/// Run the error message loop, for 5 seconds, with countdown fn err_loop( - now: &mut Instant, terminal: &mut Terminal, ) -> Result<(), AppError> { let mut seconds = 5; + let mut now = Instant::now(); loop { + // This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout + execute!(io::stdout(), EnableMouseCapture).unwrap_or(()); + execute!(io::stdout(), DisableMouseCapture).unwrap_or(()); if seconds < 1 { - // terminal.clear().unwrap_or(()); break; } - if now.elapsed() >= std::time::Duration::from_secs(1) { seconds -= 1; - *now = Instant::now(); + now = Instant::now(); } - terminal + if terminal .draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds))) - .unwrap(); - // { - // return Err(AppError::Terminal); - // } - // terminal - // .draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds))) - // .unwrap(); + .is_err() + { + return Err(AppError::Terminal); + } } Ok(()) } +/// Run the normal application ui loop +async fn run_loop( app_data: Arc>, + docker_sx: Sender, + gui_state: Arc>, + is_running: Arc, + sender: Sender, + terminal: &mut Terminal) -> Result<(), AppError>{ + let input_poll_rate = std::time::Duration::from_millis(100); + let update_duration = + std::time::Duration::from_millis(u64::from(app_data.lock().args.docker_interval)); + let mut now = Instant::now(); + while is_running.load(Ordering::SeqCst) { + if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() { + return Err(AppError::Terminal); + } + if crossterm::event::poll(input_poll_rate).unwrap_or(false) { + if let Ok(event) = event::read() { + if let Event::Key(key) = event { + sender + .send(InputMessages::ButtonPress(key.code)) + .await + .unwrap_or(()); + } else if let Event::Mouse(m) = event { + sender + .send(InputMessages::MouseEvent(m)) + .await + .unwrap_or(()); + } else if let Event::Resize(_, _) = event { + gui_state.lock().clear_area_map(); + terminal.autoresize().unwrap_or(()); + } + } + } + + if now.elapsed() >= update_duration { + docker_sx.send(DockerMessage::Update).await.unwrap_or(()); + now = Instant::now(); + } + } + Ok(()) +} + /// Run a loop to draw the gui async fn run_app( app_data: Arc>, @@ -107,45 +144,12 @@ async fn run_app( sender: Sender, terminal: &mut Terminal, ) -> Result<(), AppError> { - let update_duration = - std::time::Duration::from_millis(u64::from(app_data.lock().args.docker_interval)); - let input_poll_rate = std::time::Duration::from_millis(75); let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]); - let mut now = Instant::now(); - - if !status_dockerconnect { - err_loop(&mut now, terminal).unwrap_or(()); + if status_dockerconnect { + err_loop(terminal).unwrap_or(()); } else { - while is_running.load(Ordering::SeqCst) { - if crossterm::event::poll(input_poll_rate).unwrap_or(false) { - if let Ok(event) = event::read() { - if let Event::Key(key) = event { - sender - .send(InputMessages::ButtonPress(key.code)) - .await - .unwrap_or(()); - } else if let Event::Mouse(m) = event { - sender - .send(InputMessages::MouseEvent(m)) - .await - .unwrap_or(()); - } else if let Event::Resize(_, _) = event { - gui_state.lock().clear_area_map(); - terminal.autoresize().unwrap_or(()); - } - } - } - - if now.elapsed() >= update_duration { - docker_sx.send(DockerMessage::Update).await.unwrap_or(()); - now = Instant::now(); - } - if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() { - return Err(AppError::Terminal); - } - } - } - + run_loop(app_data, docker_sx, gui_state, is_running, sender, terminal).await?; + } Ok(()) } From 2626250ace549f7ad64e84a9474ea48c7b3aca93 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:10:24 +0000 Subject: [PATCH 03/21] fix: stop_running function Use stop_running to set the global is_running AtomicBool to false, and to also, on a seperate thread, enable & then disable mouse capture, as was experience strange issue on Linux & WSL with mouse movements being piped to stdout --- src/docker_data/mod.rs | 8 ++----- src/input_handler/mod.rs | 8 ++----- src/main.rs | 52 +++++++++++++++++++++------------------- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 3d71a1a..8917654 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -20,6 +20,7 @@ use crate::{ app_data::{AppData, ContainerId, DockerControls}, app_error::AppError, parse_args::CliArgs, + stop_running, ui::{GuiState, Status}, ENTRY_POINT, }; @@ -408,12 +409,7 @@ impl DockerData { .values() .into_iter() .for_each(tokio::task::JoinHandle::abort); - // This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout - // execute!(std::io::stdout(), DisableMouseCapture).unwrap_or(()); - std::thread::spawn(||{ - execute!(std::io::stdout(), DisableMouseCapture).unwrap_or(()); - }); - self.is_running.store(false, Ordering::SeqCst); + stop_running(&self.is_running); } } } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 0a01fcd..cf3b91a 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -21,6 +21,7 @@ use crate::{ app_data::{AppData, DockerControls, Header}, app_error::AppError, docker_data::DockerMessage, + stop_running, ui::{GuiState, SelectablePanel, Status}, }; pub use message::InputMessages; @@ -134,12 +135,7 @@ impl InputHandler { .lock() .status_contains(&[Status::Error, Status::Init]); if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() { - // This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout - - std::thread::spawn(||{ - execute!(std::io::stdout(), DisableMouseCapture).unwrap_or(()); - }); - self.is_running.store(false, Ordering::SeqCst); + stop_running(&self.is_running); } } diff --git a/src/main.rs b/src/main.rs index 59deece..920e834 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,28 @@ #![forbid(unsafe_code)] -// #![warn( -// clippy::nursery, -// clippy::pedantic, -// clippy::expect_used, -// clippy::todo, -// clippy::unused_async, -// clippy::unwrap_used -// )] -// // Warning - These are indeed pedantic -// #![allow( -// clippy::module_name_repetitions, -// clippy::doc_markdown, -// clippy::similar_names -// )] +#![warn( + clippy::nursery, + clippy::pedantic, + clippy::expect_used, + clippy::todo, + clippy::unused_async, + clippy::unwrap_used +)] +// Warning - These are indeed pedantic +#![allow( + clippy::module_name_repetitions, + clippy::doc_markdown, + clippy::similar_names +)] // Only allow when debugging #![allow(unused)] use app_data::AppData; use app_error::AppError; use bollard::Docker; +use crossterm::{ + event::{DisableMouseCapture, EnableMouseCapture}, + execute, +}; use docker_data::DockerData; use input_handler::InputMessages; use parking_lot::Mutex; @@ -66,6 +70,15 @@ fn check_if_containerised() -> bool { } } +/// Set is_running to false, disable mouse capture, due to weird bug on Linux & WSL terminals +/// where mouse movement will be piped to the stdout +fn stop_running(is_running: &AtomicBool) { + std::thread::spawn(|| { + execute!(std::io::stdout(), EnableMouseCapture).unwrap_or(()); + execute!(std::io::stdout(), DisableMouseCapture).unwrap_or(()); + }); + is_running.store(false, std::sync::atomic::Ordering::SeqCst); +} /// Create docker daemon handler, and only spawn up the docker data handler if a ping returns non-error async fn docker_init( app_data: &Arc>, @@ -149,15 +162,4 @@ async fn main() { .await; } } - - // let mut child = std::process::Command::new("tput").arg("reset").spawn().unwrap_or_else(|e| panic!("Could not run tput: {}", e)); - // let result = child.wait().unwrap_or_else(|e| panic!("Could not wait for tput process: {}", e)); - - // if ! result.success() { - // panic!("tput failed with error code {}", result.code().unwrap()); - // } - - // Clear screen - // std::io::stdout().lock().flush().unwrap_or(()); - // std::io::stdout().lock().write(b"").unwrap_or_default(); } From c69ab4f7c3b873f25ea46958add37be78d23e9cf Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Tue, 28 Feb 2023 19:48:40 +0000 Subject: [PATCH 04/21] chore: dependencies updated --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70f8d6b..fc6722e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.6" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" +checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" dependencies = [ "bitflags", "clap_derive", @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.1.0" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" dependencies = [ "heck", "proc-macro-error", From 3437df59884f084624031fceb34ea3012a8e2251 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Tue, 28 Feb 2023 19:48:49 +0000 Subject: [PATCH 05/21] refactor: ui into a struct --- src/main.rs | 6 +- src/ui/mod.rs | 255 ++++++++++++++++++++++++++++---------------------- 2 files changed, 143 insertions(+), 118 deletions(-) diff --git a/src/main.rs b/src/main.rs index 920e834..7f4ed7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,7 +41,7 @@ mod input_handler; mod parse_args; mod ui; -use ui::{create_ui, GuiState, Status}; +use ui::{GuiState, Status, Ui}; use crate::docker_data::DockerMessage; @@ -148,9 +148,7 @@ async fn main() { handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running); if args.gui { - create_ui(app_data, docker_sx, gui_state, is_running, input_sx) - .await - .unwrap_or(()); + Ui::create(app_data, docker_sx, gui_state, is_running, input_sx).await; } else { // Debug mode for testing, mostly pointless, doesn't take terminal info!("in debug mode"); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 65a2db0..adcfb17 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -6,12 +6,13 @@ use crossterm::{ }; use parking_lot::Mutex; use std::{ - io::{self, Write}, + io::{self, Stdout, Write}, process::Stdio, sync::{atomic::Ordering, Arc}, }; use std::{sync::atomic::AtomicBool, time::Instant}; use tokio::sync::mpsc::Sender; +use tracing::error; use tui::{ backend::{Backend, CrosstermBackend}, layout::{Constraint, Direction, Layout}, @@ -29,130 +30,156 @@ use crate::{ input_handler::InputMessages, }; -/// Take control of the terminal in order to draw gui -pub async fn create_ui( +pub struct Ui { app_data: Arc>, docker_sx: Sender, gui_state: Arc>, is_running: Arc, sender: Sender, -) -> Result<()> { - enable_raw_mode()?; - let mut stdout = io::stdout(); - // EnableMouseCapture - execute!(stdout, EnableMouseCapture, EnterAlternateScreen)?; - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - - run_app( - app_data, - docker_sx, - gui_state, - is_running, - sender, - &mut terminal, - ) - .await - .unwrap_or(()); - disable_raw_mode()?; - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; - - terminal.show_cursor()?; - Ok(()) + terminal: Terminal>, } -/// Run the error message loop, for 5 seconds, with countdown -fn err_loop( - terminal: &mut Terminal, -) -> Result<(), AppError> { - let mut seconds = 5; - let mut now = Instant::now(); - loop { - // This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout - execute!(io::stdout(), EnableMouseCapture).unwrap_or(()); - execute!(io::stdout(), DisableMouseCapture).unwrap_or(()); - if seconds < 1 { - break; - } - if now.elapsed() >= std::time::Duration::from_secs(1) { - seconds -= 1; - now = Instant::now(); - } - - if terminal - .draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds))) - .is_err() - { - return Err(AppError::Terminal); +impl Ui { + /// Create a new Ui struct, and execute the drawing loops + pub async fn create( + app_data: Arc>, + docker_sx: Sender, + gui_state: Arc>, + is_running: Arc, + sender: Sender, + ) { + if let Ok(mut terminal) = Self::start_terminal() { + let mut ui = Self { + app_data, + docker_sx, + gui_state, + is_running, + sender, + terminal, + }; + if let Err(e) = ui.draw_ui().await { + error!("{e}"); + } + if let Err(e) = ui.end_terminal() { + error!("{e}"); + }; } } - Ok(()) -} - -/// Run the normal application ui loop -async fn run_loop( app_data: Arc>, - docker_sx: Sender, - gui_state: Arc>, - is_running: Arc, - sender: Sender, - terminal: &mut Terminal) -> Result<(), AppError>{ - let input_poll_rate = std::time::Duration::from_millis(100); - let update_duration = - std::time::Duration::from_millis(u64::from(app_data.lock().args.docker_interval)); - let mut now = Instant::now(); - while is_running.load(Ordering::SeqCst) { - if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() { - return Err(AppError::Terminal); - } - if crossterm::event::poll(input_poll_rate).unwrap_or(false) { - if let Ok(event) = event::read() { - if let Event::Key(key) = event { - sender - .send(InputMessages::ButtonPress(key.code)) - .await - .unwrap_or(()); - } else if let Event::Mouse(m) = event { - sender - .send(InputMessages::MouseEvent(m)) - .await - .unwrap_or(()); - } else if let Event::Resize(_, _) = event { - gui_state.lock().clear_area_map(); - terminal.autoresize().unwrap_or(()); - } - } - } - - if now.elapsed() >= update_duration { - docker_sx.send(DockerMessage::Update).await.unwrap_or(()); - now = Instant::now(); - } - } - Ok(()) -} - -/// Run a loop to draw the gui -async fn run_app( - app_data: Arc>, - docker_sx: Sender, - gui_state: Arc>, - is_running: Arc, - sender: Sender, - terminal: &mut Terminal, -) -> Result<(), AppError> { - let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]); - if status_dockerconnect { - err_loop(terminal).unwrap_or(()); - } else { - run_loop(app_data, docker_sx, gui_state, is_running, sender, terminal).await?; - } - Ok(()) + + // Setup the terminal for full-screen drawing mode, with mouse capture + fn start_terminal() -> io::Result>> { + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnableMouseCapture, EnterAlternateScreen)?; + let backend = CrosstermBackend::new(stdout); + Terminal::new(backend) + } + + /// reset the terminal back to default settings + pub fn end_terminal(&mut self) -> Result<()> { + disable_raw_mode()?; + execute!( + self.terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + self.terminal.show_cursor()?; + Ok(()) + } + + /// Draw the the error message ui, for 5 seconds, with a countdown + fn err_loop(&mut self) -> Result<(), AppError> { + let mut seconds = 5; + let mut now = Instant::now(); + loop { + // This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout + std::thread::spawn(|| { + execute!(io::stdout(), EnableMouseCapture).unwrap_or(()); + execute!(io::stdout(), DisableMouseCapture).unwrap_or(()); + }); + + if now.elapsed() >= std::time::Duration::from_secs(1) { + seconds -= 1; + now = Instant::now(); + } + + if seconds < 1 { + break; + } + + if self + .terminal + .draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds))) + .is_err() + { + return Err(AppError::Terminal); + } + } + Ok(()) + } + + /// The loop for drawing the main UI to the terminal + async fn gui_loop(&mut self) -> Result<(), AppError> { + let input_poll_rate = std::time::Duration::from_millis(100); + let update_duration = + std::time::Duration::from_millis(u64::from(self.app_data.lock().args.docker_interval)); + let mut now = Instant::now(); + while self.is_running.load(Ordering::SeqCst) { + if self + .terminal + .draw(|frame| ui(frame, &self.app_data, &self.gui_state)) + .is_err() + { + return Err(AppError::Terminal); + } + if crossterm::event::poll(input_poll_rate).unwrap_or(false) { + if let Ok(event) = event::read() { + if let Event::Key(key) = event { + self.sender + .send(InputMessages::ButtonPress(key.code)) + .await + .unwrap_or(()); + } else if let Event::Mouse(m) = event { + self.sender + .send(InputMessages::MouseEvent(m)) + .await + .unwrap_or(()); + } else if let Event::Resize(_, _) = event { + self.gui_state.lock().clear_area_map(); + self.terminal.autoresize().unwrap_or(()); + } + } + } + + if now.elapsed() >= update_duration { + self.docker_sx + .send(DockerMessage::Update) + .await + .unwrap_or(()); + now = Instant::now(); + } + } + Ok(()) + } + + + /// Draw either the Error, or main oxker ui, to the terminal + async fn draw_ui(&mut self) -> Result<(), AppError> { + let status_dockerconnect = self + .gui_state + .lock() + .status_contains(&[Status::DockerConnect]); + if status_dockerconnect { + self.err_loop()?; + } else { + self.gui_loop().await?; + } + + Ok(()) + } } +/// Draw the main ui to a frame of the terminal fn ui( f: &mut Frame<'_, B>, app_data: &Arc>, From 04c26fe8fc7c79506921b9cff42825b1ee132737 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Wed, 1 Mar 2023 19:14:39 +0000 Subject: [PATCH 06/21] refactor: improve the get_width function Just use x = x.max(y) for the column widths, instead of the previous, laborious, check then update methods --- src/app_data/mod.rs | 43 +++++++++---------------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 441c23a..02fdd38 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -428,12 +428,6 @@ impl AppData { .to_string(), ); - let rx_count = count(&container.rx.to_string()); - let tx_count = count(&container.tx.to_string()); - let image_count = count(&container.image); - let name_count = count(&container.name); - let state_count = count(&container.state.to_string()); - let status_count = count(&container.status); let mem_current_count = count( &container .mem_stats @@ -441,35 +435,16 @@ impl AppData { .unwrap_or(&ByteStats::default()) .to_string(), ); - let mem_limit_count = count(&container.mem_limit.to_string()); - if cpu_count > columns.cpu.1 { - columns.cpu.1 = cpu_count; - }; - if image_count > columns.image.1 { - columns.image.1 = image_count; - }; - if mem_current_count > columns.mem.1 { - columns.mem.1 = mem_current_count; - }; - if mem_limit_count > columns.mem.2 { - columns.mem.2 = mem_limit_count; - }; - if name_count > columns.name.1 { - columns.name.1 = name_count; - }; - if state_count > columns.state.1 { - columns.state.1 = state_count; - }; - if status_count > columns.status.1 { - columns.status.1 = status_count; - }; - if rx_count > columns.net_rx.1 { - columns.net_rx.1 = rx_count; - }; - if tx_count > columns.net_tx.1 { - columns.net_tx.1 = tx_count; - }; + columns.cpu.1 = columns.cpu.1.max(cpu_count); + columns.image.1 = columns.image.1.max(count(&container.image)); + columns.mem.1 = columns.mem.1.max(mem_current_count); + columns.mem.2 = columns.mem.2.max(count(&container.mem_limit.to_string())); + columns.name.1 = columns.name.1.max(count(&container.name)); + columns.net_rx.1 = columns.net_rx.1.max(count(&container.rx.to_string())); + columns.net_tx.1 = columns.net_tx.1.max(count(&container.tx.to_string())); + columns.state.1 = columns.state.1.max(count(&container.state.to_string())); + columns.status.1 = columns.status.1.max(count(&container.status)); } columns } From fdc67c9249a239bac97a78b20c9378472865209c Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Wed, 1 Mar 2023 19:17:00 +0000 Subject: [PATCH 07/21] docs: readme typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f7771b..aa4ee99 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ In application controls | button| result| |--|--| | ```( tab )``` or ```( shift+tab )``` | change panel, clicking on a panel also changes the selected panel| -| ```( ↑ ↓ )``` or ```( j k )``` or ```(PgUp PgDown)``` or ```(Home End)```| change selected line in selected panel, mouse scroll also changes selected line | +| ```( ↑ ↓ )``` or ```( j k )``` or ```( PgUp PgDown )``` or ```( Home End )```| change selected line in selected panel, mouse scroll also changes selected line | | ```( enter )```| execute selected docker command| | ```( 1-9 )``` | sort containers by heading, clicking on headings also sorts the selected column | | ```( 0 )``` | stop sorting | From 28de74b866f07c8543e46be3cab929eff28953fd Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Wed, 1 Mar 2023 19:18:19 +0000 Subject: [PATCH 08/21] feat: style help info box Style each button comman in the help information window, instead of just one giant string. Now uses a HelpInfo struct, which contains the content, as well as widths + height --- src/ui/draw_blocks.rs | 274 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 221 insertions(+), 53 deletions(-) diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index 309b2ab..bc24b24 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -481,93 +481,249 @@ pub fn heading_bar( /// From a given &str, return the maximum number of chars on a single line fn max_line_width(text: &str) -> usize { - let mut max_line_width = 0; - text.lines().into_iter().for_each(|line| { - let width = line.chars().count(); - if width > max_line_width { - max_line_width = width; + text.lines() + .into_iter() + .map(|i| i.chars().count()) + .max() + .unwrap_or_default() +} + +/// Help popup box needs these three pieces of information +struct HelpInfo { + spans: Vec>, + width: usize, + height: usize, +} + +impl HelpInfo { + /// Find the max width of a Span in &[Spans], although it isn't calulating it correctly + fn calc_width(spans: &[Spans]) -> usize { + spans + .iter() + .flat_map(|x| x.0.iter()) + .map(tui::text::Span::width) + .max() + .unwrap_or(1) + } + + /// Just an empty span, i.e. a new line + fn empty_span<'a>() -> Spans<'a> { + Spans::from(String::new()) + } + + /// generate a span, of given &str and given color + fn span<'a>(input: &str, color: Color) -> Span<'a> { + Span::styled(input.to_owned(), Style::default().fg(color)) + } + + /// Span to black text span + fn black_span<'a>(input: &str) -> Span<'a> { + Self::span(input, Color::Black) + } + + /// Span to white text span + fn white_span<'a>(input: &str) -> Span<'a> { + Self::span(input, Color::White) + } + + fn gen_name() -> Self { + let mut spans = NAME_TEXT + .lines() + .into_iter() + .map(|i| Spans::from(Self::white_span(i))) + .collect::>(); + spans.insert(0, Self::empty_span()); + let width = Self::calc_width(&spans); + let height = spans.len(); + Self { + spans, + width, + height, } - }); - max_line_width + } + + fn gen_description() -> Self { + let spans = [ + Self::empty_span(), + Spans::from(Self::white_span(DESCRIPTION)), + Self::empty_span(), + ]; + let width = Self::calc_width(&spans); + let height = spans.len(); + Self { + spans: spans.to_vec(), + width, + height, + } + } + + fn gen_button() -> Self { + let button_item = |x: &str| Self::white_span(&format!(" {x} ")); + let button_desc = |x: &str| Self::black_span(x); + let or = || button_desc("or"); + let space = || button_desc(" "); + + let spans = [ + Spans::from(vec![ + space(), + button_item("( tab )"), + or(), + button_item("( shift+tab )"), + button_desc("to change panels"), + ]), + Spans::from(vec![ + space(), + button_item("( ↑ ↓ )"), + or(), + button_item("( j k )"), + or(), + button_item("( PgUp PgDown )"), + or(), + button_item("( Home End )"), + button_desc("to change selected line"), + ]), + Spans::from(vec![ + space(), + button_item("( enter )"), + button_desc("to send docker container command"), + ]), + Spans::from(vec![ + space(), + button_item("( h )"), + button_desc("to toggle this help information"), + ]), + Spans::from(vec![ + space(), + button_item("( 0 )"), + button_desc("to stop sort"), + ]), + Spans::from(vec![ + space(), + button_item("( 1 - 9 )"), + button_desc("sort by header - or click header"), + ]), + Spans::from(vec![ + space(), + button_item("( m )"), + button_desc( + "to toggle mouse capture - if disabled, text on screen can be selected & copied", + ), + ]), + Spans::from(vec![ + space(), + button_item("( q )"), + button_desc("to quit at any time"), + ]), + ]; + + let height = spans.len(); + let width = Self::calc_width(&spans); + Self { + spans: spans.to_vec(), + width, + height, + } + } + + fn gen_final() -> Self { + let spans = [ + Self::empty_span(), + Spans::from(vec![Self::black_span( + "currently an early work in progress, all and any input appreciated", + )]), + Spans::from(vec![Span::styled( + REPO.to_owned(), + Style::default() + .bg(Color::Magenta) + .fg(Color::Black) + .add_modifier(Modifier::UNDERLINED), + )]), + ]; + let height = spans.len(); + let width = Self::calc_width(&spans); + Self { + spans: spans.to_vec(), + width, + height, + } + } } /// Draw the help box in the centre of the screen -/// TODO should make every line it's own renderable span pub fn help_box(f: &mut Frame<'_, B>) { let title = format!(" {VERSION} "); - let description_text = format!("\n{DESCRIPTION}"); + let name_info = HelpInfo::gen_name(); + let description_info = HelpInfo::gen_description(); + let button_info = HelpInfo::gen_button(); + let final_info = HelpInfo::gen_final(); - let mut help_text = String::from("\n ( tab ) or ( shift+tab ) to change panels"); - help_text - .push_str("\n ( ↑ ↓ ) or ( j k ) or (PgUp PgDown) or (Home End) to change selected line"); - help_text.push_str("\n ( enter ) to send docker container commands"); - help_text.push_str("\n ( h ) to toggle this help information"); - help_text.push_str("\n ( 0 ) stop sort"); - help_text.push_str("\n ( 1 - 9 ) sort by header - or click header"); - help_text.push_str( - "\n ( m ) to toggle mouse capture - if disabled, text on screen can be selected & copied", + // have to add 10, but shouldn't need to, is an error somewhere + let max_line_width = [ + name_info.width, + description_info.width, + button_info.width, + final_info.width, + ] + .into_iter() + .max() + .unwrap_or_default() + + 10; + let max_height = + name_info.height + description_info.height + button_info.height + final_info.height + 2; + + let area = popup( + max_height, + max_line_width, + f.size(), + BoxLocation::MiddleCentre, ); - help_text.push_str("\n ( q ) to quit at any time"); - help_text.push_str("\n mouse scrolling & clicking also available"); - help_text.push_str("\n\n currently an early work in progress, all and any input appreciated"); - help_text.push_str(format!("\n {}", REPO.trim()).as_str()); - // Find the maximum line widths & height - let all_text = format!("{NAME_TEXT}{description_text}{help_text}"); - let mut max_line_width = max_line_width(&all_text); - let mut lines = all_text.lines().count(); + let split_popup = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Max(name_info.height.try_into().unwrap_or_default()), + Constraint::Max(description_info.height.try_into().unwrap_or_default()), + Constraint::Max(button_info.height.try_into().unwrap_or_default()), + Constraint::Max(final_info.height.try_into().unwrap_or_default()), + ] + .as_ref(), + ) + .split(area); - // Add some vertical and horizontal padding to the info box - lines += 3; - max_line_width += 4; - - let name_paragraph = Paragraph::new(NAME_TEXT) + let name_paragraph = Paragraph::new(name_info.spans) .style(Style::default().bg(Color::Magenta).fg(Color::White)) .block(Block::default()) .alignment(Alignment::Center); - let description_paragraph = Paragraph::new(description_text.as_str()) + let description_paragraph = Paragraph::new(description_info.spans) .style(Style::default().bg(Color::Magenta).fg(Color::Black)) .block(Block::default()) .alignment(Alignment::Center); - let help_paragraph = Paragraph::new(help_text.as_str()) + let help_paragraph = Paragraph::new(button_info.spans) .style(Style::default().bg(Color::Magenta).fg(Color::Black)) .block(Block::default()) .alignment(Alignment::Left); + let final_paragraph = Paragraph::new(final_info.spans) + .style(Style::default().bg(Color::Magenta).fg(Color::Black)) + .block(Block::default()) + .alignment(Alignment::Center); + let block = Block::default() .title(title) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(Color::Black)); - let area = popup(lines, max_line_width, f.size(), BoxLocation::MiddleCentre); - - let split_popup = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Max(NAME_TEXT.lines().count().try_into().unwrap_or_default()), - Constraint::Max( - description_text - .lines() - .count() - .try_into() - .unwrap_or_default(), - ), - Constraint::Max(help_text.lines().count().try_into().unwrap_or_default()), - ] - .as_ref(), - ) - .split(area); - // Order is important here f.render_widget(Clear, area); f.render_widget(name_paragraph, split_popup[0]); f.render_widget(description_paragraph, split_popup[1]); f.render_widget(help_paragraph, split_popup[2]); + f.render_widget(final_paragraph, split_popup[3]); f.render_widget(block, area); } @@ -668,3 +824,15 @@ fn popup(text_lines: usize, text_width: usize, r: Rect, box_location: BoxLocatio .constraints(h_constraints) .split(popup_layout[indexes.0])[indexes.1] } + +// Draw nothing, as in a blank screen +// pub fn nothing(f: &mut Frame<'_, B>) { +// let whole_layout = Layout::default() +// .direction(Direction::Vertical) +// .constraints([Constraint::Min(100)].as_ref()) +// .split(f.size()); + +// let block = Block::default() +// .borders(Borders::NONE); +// f.render_widget(block, whole_layout[0]); +// } From b8f5792d1865d3a398cd7f23aa9473a55dc6ea44 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Wed, 1 Mar 2023 19:18:36 +0000 Subject: [PATCH 09/21] refactor: dead code removed --- src/docker_data/mod.rs | 7 +----- src/main.rs | 7 ++---- src/ui/mod.rs | 51 ++++++++++++++++++++++-------------------- 3 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 8917654..4e7923b 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -3,15 +3,11 @@ use bollard::{ service::ContainerSummary, Docker, }; -use crossterm::{event::DisableMouseCapture, execute}; use futures_util::StreamExt; use parking_lot::Mutex; use std::{ collections::HashMap, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::{atomic::AtomicBool, Arc}, }; use tokio::{sync::mpsc::Receiver, task::JoinHandle}; use uuid::Uuid; @@ -438,7 +434,6 @@ impl DockerData { spawns: Arc::new(Mutex::new(HashMap::new())), }; inner.initialise_container_data().await; - inner.message_handler().await; } } diff --git a/src/main.rs b/src/main.rs index 7f4ed7f..d2ee241 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ clippy::similar_names )] // Only allow when debugging -#![allow(unused)] +// #![allow(unused)] use app_data::AppData; use app_error::AppError; @@ -27,10 +27,7 @@ use docker_data::DockerData; use input_handler::InputMessages; use parking_lot::Mutex; use parse_args::CliArgs; -use std::{ - io::Write, - sync::{atomic::AtomicBool, Arc}, -}; +use std::sync::{atomic::AtomicBool, Arc}; use tokio::sync::mpsc::{Receiver, Sender}; use tracing::{info, Level}; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index adcfb17..58dd9a0 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -6,8 +6,7 @@ use crossterm::{ }; use parking_lot::Mutex; use std::{ - io::{self, Stdout, Write}, - process::Stdio, + io::{self, Stdout}, sync::{atomic::Ordering, Arc}, }; use std::{sync::atomic::AtomicBool, time::Instant}; @@ -35,12 +34,13 @@ pub struct Ui { docker_sx: Sender, gui_state: Arc>, is_running: Arc, + now: Instant, sender: Sender, terminal: Terminal>, } impl Ui { - /// Create a new Ui struct, and execute the drawing loops + /// Create a new Ui struct, and execute the drawing loop pub async fn create( app_data: Arc>, docker_sx: Sender, @@ -48,35 +48,40 @@ impl Ui { is_running: Arc, sender: Sender, ) { - if let Ok(mut terminal) = Self::start_terminal() { + if let Ok(terminal) = Self::setup_terminal() { let mut ui = Self { app_data, docker_sx, gui_state, is_running, + now: Instant::now(), sender, terminal, }; if let Err(e) = ui.draw_ui().await { error!("{e}"); } - if let Err(e) = ui.end_terminal() { + if let Err(e) = ui.reset_terminal() { error!("{e}"); }; + } else { + error!("Terminal Error"); } } - // Setup the terminal for full-screen drawing mode, with mouse capture - fn start_terminal() -> io::Result>> { + /// Setup the terminal for full-screen drawing mode, with mouse capture + fn setup_terminal() -> io::Result>> { enable_raw_mode()?; let mut stdout = io::stdout(); - execute!(stdout, EnableMouseCapture, EnterAlternateScreen)?; + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); Terminal::new(backend) } /// reset the terminal back to default settings - pub fn end_terminal(&mut self) -> Result<()> { + pub fn reset_terminal(&mut self) -> Result<()> { + self.terminal.clear()?; + disable_raw_mode()?; execute!( self.terminal.backend_mut(), @@ -90,20 +95,19 @@ impl Ui { /// Draw the the error message ui, for 5 seconds, with a countdown fn err_loop(&mut self) -> Result<(), AppError> { let mut seconds = 5; - let mut now = Instant::now(); loop { - // This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout + // This is a fix for a weird bug on Linux + WSL which will output mouse movement to stdout std::thread::spawn(|| { execute!(io::stdout(), EnableMouseCapture).unwrap_or(()); execute!(io::stdout(), DisableMouseCapture).unwrap_or(()); }); - if now.elapsed() >= std::time::Duration::from_secs(1) { + if self.now.elapsed() >= std::time::Duration::from_secs(1) { seconds -= 1; - now = Instant::now(); + self.now = Instant::now(); } - - if seconds < 1 { + + if seconds < 1 { break; } @@ -120,14 +124,14 @@ impl Ui { /// The loop for drawing the main UI to the terminal async fn gui_loop(&mut self) -> Result<(), AppError> { - let input_poll_rate = std::time::Duration::from_millis(100); + let input_poll_rate = std::time::Duration::from_millis(1); let update_duration = std::time::Duration::from_millis(u64::from(self.app_data.lock().args.docker_interval)); - let mut now = Instant::now(); + while self.is_running.load(Ordering::SeqCst) { if self .terminal - .draw(|frame| ui(frame, &self.app_data, &self.gui_state)) + .draw(|frame| ui_frame(frame, &self.app_data, &self.gui_state)) .is_err() { return Err(AppError::Terminal); @@ -151,19 +155,18 @@ impl Ui { } } - if now.elapsed() >= update_duration { + if self.now.elapsed() >= update_duration { self.docker_sx .send(DockerMessage::Update) .await .unwrap_or(()); - now = Instant::now(); + self.now = Instant::now(); } } Ok(()) } - - /// Draw either the Error, or main oxker ui, to the terminal + /// Draw either the Error, or main oxker ui, to the terminal async fn draw_ui(&mut self) -> Result<(), AppError> { let status_dockerconnect = self .gui_state @@ -180,7 +183,7 @@ impl Ui { } /// Draw the main ui to a frame of the terminal -fn ui( +fn ui_frame( f: &mut Frame<'_, B>, app_data: &Arc>, gui_state: &Arc>, @@ -232,7 +235,7 @@ fn ui( vec![Constraint::Percentage(100)] }; - // Split into 3, containers+controls, logs, then graphs + // Split into 2, logs, and optional charts let lower_main = Layout::default() .direction(Direction::Vertical) .constraints(lower_split.as_ref()) From ec962295a8789ff8010604e974969bf618ea7108 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Wed, 1 Mar 2023 21:33:58 +0000 Subject: [PATCH 10/21] docs: comments improved --- src/ui/draw_blocks.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index bc24b24..f03a68d 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -496,7 +496,7 @@ struct HelpInfo { } impl HelpInfo { - /// Find the max width of a Span in &[Spans], although it isn't calulating it correctly + /// Find the max width of a Span in &[Spans], although it isn't calculating it correctly fn calc_width(spans: &[Spans]) -> usize { spans .iter() @@ -526,6 +526,7 @@ impl HelpInfo { Self::span(input, Color::White) } + /// Generate the `oxker` name span + metadata fn gen_name() -> Self { let mut spans = NAME_TEXT .lines() @@ -542,6 +543,7 @@ impl HelpInfo { } } + /// Generate the description span + metadata fn gen_description() -> Self { let spans = [ Self::empty_span(), @@ -557,6 +559,7 @@ impl HelpInfo { } } + /// Generate the button information span + metadata fn gen_button() -> Self { let button_item = |x: &str| Self::white_span(&format!(" {x} ")); let button_desc = |x: &str| Self::black_span(x); @@ -625,6 +628,7 @@ impl HelpInfo { } } + /// Generate the final lines, GitHub link etc, + metadata fn gen_final() -> Self { let spans = [ Self::empty_span(), From e650034d50f01a7598876d4f2887df691700e06a Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Wed, 1 Mar 2023 21:34:16 +0000 Subject: [PATCH 11/21] chore: devcontainer install x86 musl target --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5dca4c6..0cdbe41 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,7 +17,7 @@ "seccomp=unconfined" ], - "postCreateCommand": "cargo install cross typos-cli", + "postCreateCommand": "rustup target add x86_64-unknown-linux-musl && cargo install cross typos-cli", "mounts": [ "source=/etc/timezone,target=/etc/timezone,type=bind,readonly" From 0a1b53111627206cc7436589e5b7212e1b72edb8 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 01:09:17 +0000 Subject: [PATCH 12/21] feat: Acutal fix the the mouse events output The EnableMouseCapture from Crossterm was too broad, by only enabling a subject of the events, 1) performance is improvedand 2) and intermittent bug where mouse events were output to stdout has been removed --- containerised/Dockerfile_dev | 2 +- src/docker_data/mod.rs | 4 ++-- src/input_handler/mod.rs | 23 ++++++++--------------- src/main.rs | 13 ------------- src/ui/mod.rs | 33 +++++++++++++++++++++------------ 5 files changed, 32 insertions(+), 43 deletions(-) diff --git a/containerised/Dockerfile_dev b/containerised/Dockerfile_dev index 88accee..6ff45ba 100644 --- a/containerised/Dockerfile_dev +++ b/containerised/Dockerfile_dev @@ -14,7 +14,7 @@ COPY ./target/x86_64-unknown-linux-musl/release/oxker /app/ ENTRYPOINT [ "/app/oxker"] # Dev build for testing -# docker build -t oxker_dev -f Dockerfile . && docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro oxker_dev +# docker build -t oxker_dev -f containerised/Dockerfile_dev . && docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro oxker_dev # Dev build one liner, x86 host # docker image prune -a; cargo build --release --target x86_64-unknown-linux-musl && docker build -t oxker_dev -f containerised/Dockerfile_dev . && docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro oxker_dev diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 4e7923b..51b955e 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -16,7 +16,6 @@ use crate::{ app_data::{AppData, ContainerId, DockerControls}, app_error::AppError, parse_args::CliArgs, - stop_running, ui::{GuiState, Status}, ENTRY_POINT, }; @@ -405,7 +404,8 @@ impl DockerData { .values() .into_iter() .for_each(tokio::task::JoinHandle::abort); - stop_running(&self.is_running); + self.is_running + .store(false, std::sync::atomic::Ordering::SeqCst); } } } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index cf3b91a..f1dd26c 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -4,9 +4,7 @@ use std::sync::{ }; use crossterm::{ - event::{ - DisableMouseCapture, EnableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind, - }, + event::{DisableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind}, execute, }; use parking_lot::Mutex; @@ -21,8 +19,7 @@ use crate::{ app_data::{AppData, DockerControls, Header}, app_error::AppError, docker_data::DockerMessage, - stop_running, - ui::{GuiState, SelectablePanel, Status}, + ui::{enable_mouse_capture, GuiState, SelectablePanel, Status}, }; pub use message::InputMessages; @@ -95,15 +92,10 @@ impl InputHandler { } } } else { - match execute!(std::io::stdout(), EnableMouseCapture) { - Ok(_) => self - .gui_state - .lock() - .set_info_box("✓ mouse capture enabled".to_owned()), - Err(_) => { - self.app_data.lock().set_error(AppError::MouseCapture(true)); - } - } + enable_mouse_capture(); + self.gui_state + .lock() + .set_info_box("✓ mouse capture enabled".to_owned()); }; // If the info box sleep handle is currently being executed, as in 'm' is pressed twice within a 4000ms window @@ -135,7 +127,8 @@ impl InputHandler { .lock() .status_contains(&[Status::Error, Status::Init]); if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() { - stop_running(&self.is_running); + self.is_running + .store(false, std::sync::atomic::Ordering::SeqCst); } } diff --git a/src/main.rs b/src/main.rs index d2ee241..ec9eef4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,10 +19,6 @@ use app_data::AppData; use app_error::AppError; use bollard::Docker; -use crossterm::{ - event::{DisableMouseCapture, EnableMouseCapture}, - execute, -}; use docker_data::DockerData; use input_handler::InputMessages; use parking_lot::Mutex; @@ -67,15 +63,6 @@ fn check_if_containerised() -> bool { } } -/// Set is_running to false, disable mouse capture, due to weird bug on Linux & WSL terminals -/// where mouse movement will be piped to the stdout -fn stop_running(is_running: &AtomicBool) { - std::thread::spawn(|| { - execute!(std::io::stdout(), EnableMouseCapture).unwrap_or(()); - execute!(std::io::stdout(), DisableMouseCapture).unwrap_or(()); - }); - is_running.store(false, std::sync::atomic::Ordering::SeqCst); -} /// Create docker daemon handler, and only spawn up the docker data handler if a ping returns non-error async fn docker_init( app_data: &Arc>, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 58dd9a0..9522b51 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,12 +1,12 @@ use anyhow::Result; use crossterm::{ - event::{self, DisableMouseCapture, EnableMouseCapture, Event}, + event::{self, DisableMouseCapture, Event}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use parking_lot::Mutex; use std::{ - io::{self, Stdout}, + io::{self, Stdout, Write}, sync::{atomic::Ordering, Arc}, }; use std::{sync::atomic::AtomicBool, time::Instant}; @@ -39,6 +39,20 @@ pub struct Ui { terminal: Terminal>, } +/// Enable moust capture, but don't enable all the mouse movements, which improves performance, and fixes the weird mouse event output bug +pub fn enable_mouse_capture() { + io::stdout() + .write_all( + concat!( + crossterm::csi!("?1000h"), + crossterm::csi!("?1015h"), + crossterm::csi!("?1006h"), + ) + .as_bytes(), + ) + .unwrap_or(()); +} + impl Ui { /// Create a new Ui struct, and execute the drawing loop pub async fn create( @@ -73,7 +87,8 @@ impl Ui { fn setup_terminal() -> io::Result>> { enable_raw_mode()?; let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + execute!(stdout, EnterAlternateScreen)?; + enable_mouse_capture(); let backend = CrosstermBackend::new(stdout); Terminal::new(backend) } @@ -96,12 +111,6 @@ impl Ui { fn err_loop(&mut self) -> Result<(), AppError> { let mut seconds = 5; loop { - // This is a fix for a weird bug on Linux + WSL which will output mouse movement to stdout - std::thread::spawn(|| { - execute!(io::stdout(), EnableMouseCapture).unwrap_or(()); - execute!(io::stdout(), DisableMouseCapture).unwrap_or(()); - }); - if self.now.elapsed() >= std::time::Duration::from_secs(1) { seconds -= 1; self.now = Instant::now(); @@ -124,14 +133,14 @@ impl Ui { /// The loop for drawing the main UI to the terminal async fn gui_loop(&mut self) -> Result<(), AppError> { - let input_poll_rate = std::time::Duration::from_millis(1); + let input_poll_rate = std::time::Duration::from_millis(100); let update_duration = std::time::Duration::from_millis(u64::from(self.app_data.lock().args.docker_interval)); while self.is_running.load(Ordering::SeqCst) { if self .terminal - .draw(|frame| ui_frame(frame, &self.app_data, &self.gui_state)) + .draw(|frame| draw_frame(frame, &self.app_data, &self.gui_state)) .is_err() { return Err(AppError::Terminal); @@ -183,7 +192,7 @@ impl Ui { } /// Draw the main ui to a frame of the terminal -fn ui_frame( +fn draw_frame( f: &mut Frame<'_, B>, app_data: &Arc>, gui_state: &Arc>, From ba6437862dae0f422660a602aeabd6217d023fac Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 01:13:22 +0000 Subject: [PATCH 13/21] chore: dependencies updated --- Cargo.lock | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc6722e..b290830 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,7 +543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" dependencies = [ "libc", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -555,7 +555,7 @@ dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -634,7 +634,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -732,7 +732,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -871,7 +871,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -1125,9 +1125,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.25.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", "bytes", @@ -1140,7 +1140,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1427,21 +1427,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - [[package]] name = "windows-sys" version = "0.45.0" From 964e2a9432e12b351f8f8f8e6f30607bf50d6899 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 02:17:44 +0000 Subject: [PATCH 14/21] docs: changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a6618f..3b98961 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +### Chores ++ dependencies updated, [aac3ef2b1def3345d749d813d9b76020d6b5e5ca], [4723be7fb2eb101024bb9d5a514e2c6cc51eb6f6], [c69ab4f7c3b873f25ea46958add37be78d23e9cf], [ba6437862dae0f422660a602aeabd6217d023fac] ++ dev container install x86 musl toolchain, [e650034d50f01a7598876d4f2887df691700e06a] + +### Docs ++ typos removed, [23ad9a5fb3cacf3fb8cb70c65ca9133ed9949e45], [cebb975cb82f653407ec801fd8c726ca6ed68289], [fdc67c9249a239bac97a78b20c9378472865209c] ++ comments improved, [ec962295a8789ff8010604e974969bf618ea7108] + +### Features ++ Mouse capture is now more fined grained, should have substantial performace impact on low end machines, as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8] ++ Improve the styling of the help information popup, [28de74b866f07c8543e46be3cab929eff28953fd] ++ use checked_sub & checked_div for bounds checks, [72279e26ae996353c95a75527f704bac1e4bcf4d] + +### Refactors ++ Dead code removed, [b8f5792d1865d3a398cd7f23aa9473a55dc6ea44] ++ improve the get_width function, [04c26fe8fc7c79506921b9cff42825b1ee132737] ++ Place ui methods into a Ui struct, [3437df59884f084624031fceb34ea3012a8e2251] ++ get_horizotal/vertical contraints into single method, [e8f5cf9c6f8cd5f807a05fb61e31d7cd1426486f] ++ docker update_everything variables, [074cb957f274675a468f08fecb1c43ff7453217d] + # v0.2.3 ### 2023-02-04 From 495d5ae789ac7b1aaac8fd796eb8110592c64c4e Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 03:20:35 +0000 Subject: [PATCH 15/21] docs: changelog --- CHANGELOG.md | 4 ++-- src/ui/mod.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b98961..7c2d495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ + comments improved, [ec962295a8789ff8010604e974969bf618ea7108] ### Features -+ Mouse capture is now more fined grained, should have substantial performace impact on low end machines, as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8] ++ Mouse capture is now more specific, should have substantial performance impact on low end machines (i.e. raspberry pi), as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8] + Improve the styling of the help information popup, [28de74b866f07c8543e46be3cab929eff28953fd] + use checked_sub & checked_div for bounds checks, [72279e26ae996353c95a75527f704bac1e4bcf4d] @@ -15,7 +15,7 @@ + Dead code removed, [b8f5792d1865d3a398cd7f23aa9473a55dc6ea44] + improve the get_width function, [04c26fe8fc7c79506921b9cff42825b1ee132737] + Place ui methods into a Ui struct, [3437df59884f084624031fceb34ea3012a8e2251] -+ get_horizotal/vertical contraints into single method, [e8f5cf9c6f8cd5f807a05fb61e31d7cd1426486f] ++ get_horizotal/vertical constraints into single method, [e8f5cf9c6f8cd5f807a05fb61e31d7cd1426486f] + docker update_everything variables, [074cb957f274675a468f08fecb1c43ff7453217d] # v0.2.3 diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 9522b51..6755669 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -114,11 +114,11 @@ impl Ui { if self.now.elapsed() >= std::time::Duration::from_secs(1) { seconds -= 1; self.now = Instant::now(); + if seconds < 1 { + break; + } } - if seconds < 1 { - break; - } if self .terminal From 93f7c07f708885f8870da5dfb6d57c62f93c9c78 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 03:48:21 +0000 Subject: [PATCH 16/21] fix: nullify_event_read(), mouse event output fix --- src/ui/mod.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 6755669..8d45095 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,6 @@ use anyhow::Result; use crossterm::{ - event::{self, DisableMouseCapture, Event}, + event::{self, DisableMouseCapture, Event, EnableMouseCapture}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; @@ -8,6 +8,7 @@ use parking_lot::Mutex; use std::{ io::{self, Stdout, Write}, sync::{atomic::Ordering, Arc}, + time::Duration, }; use std::{sync::atomic::AtomicBool, time::Instant}; use tokio::sync::mpsc::Sender; @@ -33,13 +34,14 @@ pub struct Ui { app_data: Arc>, docker_sx: Sender, gui_state: Arc>, + input_poll_rate: Duration, is_running: Arc, now: Instant, sender: Sender, terminal: Terminal>, } -/// Enable moust capture, but don't enable all the mouse movements, which improves performance, and fixes the weird mouse event output bug +/// Enable mouse capture, but don't enable all the mouse movements, which improves performance, and is part of the fix for the weird mouse event output bug pub fn enable_mouse_capture() { io::stdout() .write_all( @@ -67,6 +69,7 @@ impl Ui { app_data, docker_sx, gui_state, + input_poll_rate: std::time::Duration::from_millis(100), is_running, now: Instant::now(), sender, @@ -93,16 +96,23 @@ impl Ui { Terminal::new(backend) } + // This is a fix for mouse-events being printed to screen, read an event and do nothing with it + fn nullify_event_read(&self) { + if crossterm::event::poll(self.input_poll_rate).unwrap_or(true) { + event::read().ok(); + } + } + /// reset the terminal back to default settings pub fn reset_terminal(&mut self) -> Result<()> { self.terminal.clear()?; - disable_raw_mode()?; execute!( self.terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture )?; + disable_raw_mode()?; self.terminal.show_cursor()?; Ok(()) } @@ -114,11 +124,13 @@ impl Ui { if self.now.elapsed() >= std::time::Duration::from_secs(1) { seconds -= 1; self.now = Instant::now(); - if seconds < 1 { - break; - } + if seconds < 1 { + break; + } } + // This is a fix for mouse-events being printed to screen + self.nullify_event_read(); if self .terminal @@ -133,7 +145,6 @@ impl Ui { /// The loop for drawing the main UI to the terminal async fn gui_loop(&mut self) -> Result<(), AppError> { - let input_poll_rate = std::time::Duration::from_millis(100); let update_duration = std::time::Duration::from_millis(u64::from(self.app_data.lock().args.docker_interval)); @@ -145,7 +156,7 @@ impl Ui { { return Err(AppError::Terminal); } - if crossterm::event::poll(input_poll_rate).unwrap_or(false) { + if crossterm::event::poll(self.input_poll_rate).unwrap_or(false) { if let Ok(event) = event::read() { if let Event::Key(key) = event { self.sender @@ -186,7 +197,7 @@ impl Ui { } else { self.gui_loop().await?; } - + self.nullify_event_read(); Ok(()) } } From 9d2e72086b8372ad865a84d91807a9f97b37755b Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 03:54:55 +0000 Subject: [PATCH 17/21] docs: comment typos --- src/app_data/container_state.rs | 2 +- src/app_data/mod.rs | 6 ++---- src/ui/mod.rs | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index aedc613..b1e1ba6 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -142,7 +142,7 @@ impl State { _ => Color::Red, } } - // Dirty way to create order for the state, rather than impl Ord + /// Dirty way to create order for the state, rather than impl Ord pub const fn order(self) -> u8 { match self { Self::Running => 0, diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 02fdd38..3169b1e 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -203,7 +203,7 @@ impl AppData { self.containers.start(); } - // select the last container + /// select the last container pub fn containers_end(&mut self) { self.containers.end(); } @@ -213,7 +213,7 @@ impl AppData { self.containers.next(); } - // select the previous container + /// select the previous container pub fn containers_previous(&mut self) { self.containers.previous(); } @@ -494,9 +494,7 @@ impl AppData { container.mem_limit.update(mem_limit); } // need to benchmark this? - // if self.get_sorted().is_some() { self.sort_containers(); - // } } /// Update, or insert, containers diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 8d45095..dcbc394 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,6 @@ use anyhow::Result; use crossterm::{ - event::{self, DisableMouseCapture, Event, EnableMouseCapture}, + event::{self, DisableMouseCapture, Event}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; @@ -96,7 +96,7 @@ impl Ui { Terminal::new(backend) } - // This is a fix for mouse-events being printed to screen, read an event and do nothing with it + /// This is a fix for mouse-events being printed to screen, read an event and do nothing with it fn nullify_event_read(&self) { if crossterm::event::poll(self.input_poll_rate).unwrap_or(true) { event::read().ok(); From c74f6c1179b5f62989eb74f395a56b43a8781b03 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 04:10:38 +0000 Subject: [PATCH 18/21] refactor: enable_mouse_capture() moved into Ui struct --- src/input_handler/mod.rs | 4 ++-- src/ui/mod.rs | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index f1dd26c..aa9a866 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -19,7 +19,7 @@ use crate::{ app_data::{AppData, DockerControls, Header}, app_error::AppError, docker_data::DockerMessage, - ui::{enable_mouse_capture, GuiState, SelectablePanel, Status}, + ui::{GuiState, SelectablePanel, Status, Ui}, }; pub use message::InputMessages; @@ -92,7 +92,7 @@ impl InputHandler { } } } else { - enable_mouse_capture(); + Ui::enable_mouse_capture(); self.gui_state .lock() .set_info_box("✓ mouse capture enabled".to_owned()); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index dcbc394..867c7e1 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -41,21 +41,21 @@ pub struct Ui { terminal: Terminal>, } -/// Enable mouse capture, but don't enable all the mouse movements, which improves performance, and is part of the fix for the weird mouse event output bug -pub fn enable_mouse_capture() { - io::stdout() - .write_all( - concat!( - crossterm::csi!("?1000h"), - crossterm::csi!("?1015h"), - crossterm::csi!("?1006h"), - ) - .as_bytes(), - ) - .unwrap_or(()); -} - impl Ui { + /// Enable mouse capture, but don't enable all the mouse movements, which improves performance, and is part of the fix for the weird mouse event output bug + pub fn enable_mouse_capture() { + io::stdout() + .write_all( + concat!( + crossterm::csi!("?1000h"), + crossterm::csi!("?1015h"), + crossterm::csi!("?1006h"), + ) + .as_bytes(), + ) + .unwrap_or(()); + } + /// Create a new Ui struct, and execute the drawing loop pub async fn create( app_data: Arc>, @@ -91,7 +91,7 @@ impl Ui { enable_raw_mode()?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen)?; - enable_mouse_capture(); + Self::enable_mouse_capture(); let backend = CrosstermBackend::new(stdout); Terminal::new(backend) } From cb7518afe61632b36f4fa73efa8737a7546ef500 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 04:13:31 +0000 Subject: [PATCH 19/21] docs: changelog --- CHANGELOG.md | 2 +- src/ui/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c2d495..97a64d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ + comments improved, [ec962295a8789ff8010604e974969bf618ea7108] ### Features -+ Mouse capture is now more specific, should have substantial performance impact on low end machines (i.e. raspberry pi), as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8] ++ Mouse capture is now more specific, should have substantial performance impact on low end machines (i.e. raspberry pi), as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8], [93f7c07f708885f8870da5dfb6d57c62f93c9c78], [c74f6c1179b5f62989eb74f395a56b43a8781b03] + Improve the styling of the help information popup, [28de74b866f07c8543e46be3cab929eff28953fd] + use checked_sub & checked_div for bounds checks, [72279e26ae996353c95a75527f704bac1e4bcf4d] diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 867c7e1..2ee8788 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -42,7 +42,7 @@ pub struct Ui { } impl Ui { - /// Enable mouse capture, but don't enable all the mouse movements, which improves performance, and is part of the fix for the weird mouse event output bug + /// Enable mouse capture, but don't enable capture of all the mouse movements, doing so will improve performance, and is part of the fix for the weird mouse event output bug pub fn enable_mouse_capture() { io::stdout() .write_all( From 4500ba46e1415c7ccbeed22ef0832b36e1a286dc Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 04:14:13 +0000 Subject: [PATCH 20/21] docs: changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97a64d3..49e80d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ + comments improved, [ec962295a8789ff8010604e974969bf618ea7108] ### Features -+ Mouse capture is now more specific, should have substantial performance impact on low end machines (i.e. raspberry pi), as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8], [93f7c07f708885f8870da5dfb6d57c62f93c9c78], [c74f6c1179b5f62989eb74f395a56b43a8781b03] ++ Mouse capture is now more specific, should have substantial performance impact, especially on low end machines (i.e. raspberry pi), as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8], [93f7c07f708885f8870da5dfb6d57c62f93c9c78], [c74f6c1179b5f62989eb74f395a56b43a8781b03] + Improve the styling of the help information popup, [28de74b866f07c8543e46be3cab929eff28953fd] + use checked_sub & checked_div for bounds checks, [72279e26ae996353c95a75527f704bac1e4bcf4d] From bd33b0b31a16c58a4430fa3db4bbb7eb9045c1fd Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 2 Mar 2023 04:21:43 +0000 Subject: [PATCH 21/21] docs: readme --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49e80d8..a6f88c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ + comments improved, [ec962295a8789ff8010604e974969bf618ea7108] ### Features -+ Mouse capture is now more specific, should have substantial performance impact, especially on low end machines (i.e. raspberry pi), as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8], [93f7c07f708885f8870da5dfb6d57c62f93c9c78], [c74f6c1179b5f62989eb74f395a56b43a8781b03] ++ Mouse capture is now more specific, should have substantial performance impact, two to four time reduction in cpu usage when mouse is moved, especially on low end machines (i.e. raspberry pi), as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8], [93f7c07f708885f8870da5dfb6d57c62f93c9c78], [c74f6c1179b5f62989eb74f395a56b43a8781b03] + Improve the styling of the help information popup, [28de74b866f07c8543e46be3cab929eff28953fd] + use checked_sub & checked_div for bounds checks, [72279e26ae996353c95a75527f704bac1e4bcf4d]