diff --git a/README.md b/README.md index 00f8b97..513a4dc 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ In application controls | ```( h )``` | Toggle help menu.| | ```( m )``` | Toggle mouse capture - if disabled, text on screen can be selected.| | ```( q )``` | Quit.| -| ```( s )``` | Save logs to `$HOME/[container_name]_[timestamp].log`, or the directory set by `--logs-dir`.| +| ```( s )``` | Save logs to `$HOME/oxker_[container_name].log`, or the directory set by `--save-dir`.| Available command line arguments @@ -114,8 +114,8 @@ Available command line arguments |```-s```| If running via Docker, will display the oxker container.| |```-g```| No TUI, essentially a debugging mode with limited functionality, for now.| |```--host [hostname]```| Connect to Docker with a custom hostname. Defaults to `/var/run/docker.sock`. Will use `$DOCKER_HOST` environment variable if set.| -|```--use-cli```| When executing into a container, use the external Docker CLI application.| -|```--logs-dir```| Set a custom location to save exportings logs into. Defaults to `$HOME`.| +|```--save-dir```| Set a custom location to save exported logs into. Defaults to `$HOME`.| +|```--use-cli```| When executing into a container, force use of the external Docker CLI application.| ## Build step diff --git a/src/exec.rs b/src/exec.rs index 6dcc5db..cb81c28 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -1,15 +1,16 @@ use std::{ - io::{Read, Write}, + io::{Read, Stdout, Write}, sync::{atomic::AtomicBool, Arc}, }; use bollard::{ - exec::{CreateExecOptions, StartExecOptions, StartExecResults}, + exec::{CreateExecOptions, ResizeExecOptions, StartExecOptions, StartExecResults}, Docker, }; use crossterm::terminal::enable_raw_mode; use futures_util::StreamExt; use parking_lot::Mutex; +use ratatui::{backend::CrosstermBackend, Terminal}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::{ @@ -24,7 +25,7 @@ const TTY: &str = "/dev/tty"; const OCI_ERROR: &str = "OCI runtime exec failed"; /// Set the cursor position on the screen to (0,0) -pub const CURSOR_POS: &str = "\x1B[J\x1B[H"; +const CURSOR_POS: &str = "\x1B[J\x1B[H"; /// This needs to be written to stdout when exiting the exec mode, else the input handler thread gets confused, /// see https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement @@ -110,6 +111,24 @@ struct AsyncTTY { rx: std::sync::mpsc::Receiver, } +/// This is used to set the terminal size when exec via the Internal method +#[derive(Debug, Clone)] +pub struct TerminalSize { + width: u16, + height: u16, +} + +impl TerminalSize { + pub fn new(terminal: &Terminal>) -> Option { + terminal.size().map_or(None, |i| { + Some(Self { + width: i.width, + height: i.height, + }) + }) + } +} + #[derive(Debug, Clone)] pub enum ExecMode { // use Bollard Rust library @@ -192,7 +211,12 @@ impl ExecMode { /// Exec into the container via the Bollard library, stdout & stdin on different threads /// Have to deal with strange output once dropped, hence the use of internal_cleanup() method - async fn exec_internal(&self, id: &ContainerId, docker: &Arc) -> Result<(), AppError> { + async fn exec_internal( + &self, + id: &ContainerId, + docker: &Arc, + terminal_size: Option, + ) -> Result<(), AppError> { let run = Arc::new(AtomicBool::new(true)); if let Ok(exec_result) = docker @@ -239,6 +263,19 @@ impl ExecMode { } }); + if let Some(terminal_size) = terminal_size { + docker + .resize_exec( + &exec_result.id, + ResizeExecOptions { + height: terminal_size.height, + width: terminal_size.width, + }, + ) + .await + .ok(); + } + while let Ok(x) = async_tty.rx.recv() { input.write(&[x]).await.ok(); } @@ -263,7 +300,6 @@ impl ExecMode { let waiting_thread = Arc::clone(&waiting); std::thread::spawn(move || { - // At the moment the known max length is 26 let mut bytes = Vec::with_capacity(26); while waiting_thread.load(std::sync::atomic::Ordering::SeqCst) { let mut buf = [0]; @@ -296,15 +332,14 @@ impl ExecMode { } } - // RESET TERMINAL BEFROEHAND - pub async fn run(&self) -> Result<(), AppError> { + pub async fn run(&self, tty_size: Option) -> Result<(), AppError> { match self { Self::External(id) => { Self::exec_external(id); Ok(()) } - Self::Internal((id, docker)) => self.exec_internal(id, docker).await, + Self::Internal((id, docker)) => self.exec_internal(id, docker, tty_size).await, } } } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index a84b0d4..8da3c3c 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -187,7 +187,7 @@ impl InputHandler { let args = app_data.lock().args.clone(); let container = app_data.lock().get_selected_container_id_state_name(); if let Some((id, _, name)) = container { - if let Some(log_path) = args.logs_dir { + if let Some(log_path) = args.save_dir { let (sx, rx) = tokio::sync::oneshot::channel::>(); docker_tx.send(DockerMessage::Exec(sx)).await?; diff --git a/src/parse_args.rs b/src/parse_args.rs index a5d007d..e29d84c 100644 --- a/src/parse_args.rs +++ b/src/parse_args.rs @@ -37,13 +37,13 @@ pub struct Args { #[clap(long, short = None)] pub host: Option, - /// Use "docker" cli for execing + /// Force use of docker cli when execing into containers #[clap(long="use-cli", short = None)] pub use_cli: bool, - /// Directory for exporting logs, defaults to `$HOME` - #[clap(long="logs-dir", short = None)] - pub logs_dir: Option, + /// Directory for saving exported logs, defaults to `$HOME` + #[clap(long="save-dir", short = None)] + pub save_dir: Option, } #[derive(Debug, Clone)] @@ -54,7 +54,7 @@ pub struct CliArgs { pub gui: bool, pub host: Option, pub in_container: bool, - pub logs_dir: Option, + pub save_dir: Option, pub raw: bool, pub show_self: bool, pub timestamp: bool, @@ -77,7 +77,7 @@ impl CliArgs { pub fn new() -> Self { let args = Args::parse(); - let logs_dir = args.logs_dir.map_or_else( + let logs_dir = args.save_dir.map_or_else( || directories::BaseDirs::new().map(|base_dirs| base_dirs.home_dir().to_owned()), |logs_dir| Some(std::path::Path::new(&logs_dir).to_owned()), ); @@ -95,7 +95,7 @@ impl CliArgs { gui: !args.gui, host: args.host, in_container: Self::check_if_in_container(), - logs_dir, + save_dir: logs_dir, raw: args.raw, show_self: !args.show_self, timestamp: !args.timestamp, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 867e0d2..2df6c51 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -28,6 +28,7 @@ pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status}; use crate::{ app_data::{AppData, Columns, ContainerId, Header, SortedOrder}, app_error::AppError, + exec::TerminalSize, input_handler::InputMessages, }; @@ -147,7 +148,7 @@ impl Ui { if let Some(mode) = exec_mode { self.reset_terminal().ok(); self.terminal.clear().ok(); - if let Err(e) = mode.run().await { + if let Err(e) = mode.run(TerminalSize::new(&self.terminal)).await { self.app_data .lock() .set_error(e, &self.gui_state, Status::Error);