diff --git a/CHANGELOG.md b/CHANGELOG.md index b31b42f..c495761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ - Remove `lazy_static` dependency in favor of `std::sync::LazyLock` - MSRV is now 1.80 - Use sixel if found in device attributes instead of static TERM list +- Dont Error in kitty if the temporary file has been deleted by the terminal. (Now `KittySupport::Local` is possible again) +- Properly check for kitty support on terminals via protocol query, instead of static `TERM` environment variable checking. + - This for example allows new terminals like (KDE)`Konsole` +- When using Kitty Local & Remote, wait until the terminal has responded before returning. ## 0.9.2 - Use iterm and sixel in more terminals diff --git a/src/config.rs b/src/config.rs index 80a5bb7..0ad2a82 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use crate::utils; /// Configuration struct to customize printing behaviour. +#[derive(Debug, Clone)] pub struct Config { /// Enable true transparency instead of checkerboard background. /// Available only for the block printer. Defaults to false. diff --git a/src/lib.rs b/src/lib.rs index 0ba4d59..88a6d7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,7 @@ #[cfg(feature = "print-file")] use std::path::Path; +use console::Term; use crossterm::{ cursor::{RestorePosition, SavePosition}, execute, @@ -99,7 +100,9 @@ pub fn print(img: &DynamicImage, config: &Config) -> ViuResult<(u32, u32)> { execute!(&mut stdout, SavePosition)?; } - let (w, h) = choose_printer(config).print(&mut stdout, img, config)?; + let term = Term::stdout(); + + let (w, h) = choose_printer(config).print(&mut stdout, &term, img, config)?; if config.restore_cursor { execute!(&mut stdout, RestorePosition)?; @@ -130,7 +133,9 @@ pub fn print_from_file>(filename: P, config: &Config) -> ViuResul execute!(&mut stdout, SavePosition)?; } - let (w, h) = choose_printer(config).print_from_file(&mut stdout, filename, config)?; + let term = Term::stdout(); + + let (w, h) = choose_printer(config).print_from_file(&mut stdout, &term, filename, config)?; if config.restore_cursor { execute!(&mut stdout, RestorePosition)?; diff --git a/src/printer/block.rs b/src/printer/block.rs index 0783425..3dfb1b8 100644 --- a/src/printer/block.rs +++ b/src/printer/block.rs @@ -1,5 +1,5 @@ use crate::error::ViuResult; -use crate::printer::{adjust_offset, Printer}; +use crate::printer::{adjust_offset, Printer, ReadKey}; use crate::Config; use ansi_colours::ansi256_from_rgb; @@ -16,6 +16,7 @@ const LOWER_HALF_BLOCK: &str = "\u{2584}"; const CHECKERBOARD_BACKGROUND_LIGHT: (u8, u8, u8) = (153, 153, 153); const CHECKERBOARD_BACKGROUND_DARK: (u8, u8, u8) = (102, 102, 102); +#[derive(Debug, Clone)] pub struct BlockPrinter; impl Printer for BlockPrinter { @@ -23,6 +24,7 @@ impl Printer for BlockPrinter { &self, // TODO: The provided object is not used because termcolor needs an implementation of the WriteColor trait _stdout: &mut impl Write, + _stdin: &impl ReadKey, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { diff --git a/src/printer/icy_sixel.rs b/src/printer/icy_sixel.rs index 5613222..16c2ef7 100644 --- a/src/printer/icy_sixel.rs +++ b/src/printer/icy_sixel.rs @@ -1,4 +1,4 @@ -use super::{adjust_offset, find_best_fit, Printer}; +use super::{adjust_offset, find_best_fit, Printer, ReadKey}; use icy_sixel::sixel_string; use image::{imageops::FilterType, GenericImageView}; @@ -8,6 +8,7 @@ impl Printer for IcySixelPrinter { fn print( &self, stdout: &mut impl std::io::Write, + _stdin: &impl ReadKey, img: &image::DynamicImage, config: &crate::Config, ) -> crate::ViuResult<(u32, u32)> { diff --git a/src/printer/iterm.rs b/src/printer/iterm.rs index 3dd481c..70fd10d 100644 --- a/src/printer/iterm.rs +++ b/src/printer/iterm.rs @@ -1,5 +1,5 @@ use crate::error::ViuResult; -use crate::printer::{adjust_offset, find_best_fit, Printer}; +use crate::printer::{adjust_offset, find_best_fit, Printer, ReadKey}; use crate::Config; use base64::{engine::general_purpose, Engine}; use image::{DynamicImage, GenericImageView, ImageEncoder}; @@ -12,9 +12,6 @@ use std::{ path::Path, }; -#[allow(non_camel_case_types)] -pub struct iTermPrinter; - static ITERM_SUPPORT: LazyLock = LazyLock::new(check_iterm_support); /// Returns the terminal's support for the iTerm graphics protocol. @@ -22,10 +19,15 @@ pub fn is_iterm_supported() -> bool { *ITERM_SUPPORT } +#[allow(non_camel_case_types)] +#[derive(Debug, Clone)] +pub struct iTermPrinter; + impl Printer for iTermPrinter { fn print( &self, stdout: &mut impl Write, + _stdin: &impl ReadKey, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { @@ -47,6 +49,7 @@ impl Printer for iTermPrinter { fn print_from_file>( &self, stdout: &mut impl Write, + _stdin: &impl ReadKey, filename: P, config: &Config, ) -> ViuResult<(u32, u32)> { @@ -113,6 +116,8 @@ fn check_iterm_support() -> bool { #[cfg(test)] mod tests { + use crate::printer::TestKeys; + use super::*; use image::GenericImage; @@ -128,7 +133,12 @@ mod tests { }; let mut vec = Vec::new(); - assert_eq!(iTermPrinter.print(&mut vec, &img, &config).unwrap(), (2, 2)); + let stdin = TestKeys::new(&[]); + + assert_eq!( + iTermPrinter.print(&mut vec, &stdin, &img, &config).unwrap(), + (2, 2) + ); assert_eq!(std::str::from_utf8(&vec).unwrap(), "\x1b[4;5H\x1b]1337;File=inline=1;preserveAspectRatio=1;size=95;width=2;height=2:iVBORw0KGgoAAAANSUhEUgAAAAIAAAADCAYAAAC56t6BAAAAJklEQVR4AQEbAOT/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBAYIAEMAFdTlTsEAAAAASUVORK5CYII=\x07\n"); } } diff --git a/src/printer/kitty.rs b/src/printer/kitty.rs index 886938c..bad3e9f 100644 --- a/src/printer/kitty.rs +++ b/src/printer/kitty.rs @@ -1,15 +1,13 @@ use crate::error::{ViuError, ViuResult}; -use crate::printer::{adjust_offset, find_best_fit, Printer}; +use crate::printer::{adjust_offset, find_best_fit, Printer, ReadKey}; use crate::Config; use base64::{engine::general_purpose, Engine}; use console::{Key, Term}; -use std::io::Error; use std::io::Write; +use std::io::{Error, ErrorKind}; use std::sync::LazyLock; use tempfile::NamedTempFile; -pub struct KittyPrinter; - const TEMP_FILE_PREFIX: &str = ".tty-graphics-protocol.viuer."; static KITTY_SUPPORT: LazyLock = LazyLock::new(check_kitty_support); @@ -18,10 +16,14 @@ pub fn get_kitty_support() -> KittySupport { *KITTY_SUPPORT } +#[derive(Debug, Clone)] +pub struct KittyPrinter; + impl Printer for KittyPrinter { fn print( &self, stdout: &mut impl Write, + stdin: &impl ReadKey, img: &image::DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { @@ -29,11 +31,11 @@ impl Printer for KittyPrinter { KittySupport::None => Err(ViuError::KittyNotSupported), KittySupport::Local => { // print from file - print_local(stdout, img, config) + print_local(stdout, stdin, img, config) } KittySupport::Remote => { // print through escape codes - print_remote(stdout, img, config) + print_remote(stdout, stdin, img, config) } } } @@ -43,7 +45,7 @@ impl Printer for KittyPrinter { // fn print_from_file(&self, filename: &str, config: &Config) -> ViuResult<(u32, u32)> {} } -#[derive(PartialEq, Eq, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] /// The extend to which the Kitty graphics protocol can be used. pub enum KittySupport { /// The Kitty graphics protocol is not supported. @@ -56,45 +58,102 @@ pub enum KittySupport { // Check if Kitty protocol can be used fn check_kitty_support() -> KittySupport { - if let Ok(term) = std::env::var("TERM") { - if term.contains("kitty") || term.contains("ghostty") { - if has_local_support().is_ok() { - return KittySupport::Local; - } + let mut stdout = std::io::stdout(); + let term = Term::stdout(); - return KittySupport::Remote; + // first check if kitty protocol is generally available + if supports_kitty_protocol(&mut stdout, &term).is_ok() { + // then test if the current terminal supports reading from a file the application writes (for example this is not possible via ssh) + if has_local_support(&mut stdout, &term).is_ok() { + return KittySupport::Local; } + + return KittySupport::Remote; } + KittySupport::None } // Query the terminal whether it can display an image from a file -fn has_local_support() -> ViuResult { - // create a temp file that will hold a 1x1 image - let x = image::RgbaImage::new(1, 1); - let raw_img = x.as_raw(); - let temp_file = store_in_tmp_file(raw_img)?; - +fn supports_kitty_protocol(stdout: &mut impl Write, stdin: &impl ReadKey) -> ViuResult { // send the query - print!( - // t=t tells Kitty it's reading from a temp file and will attempt to delete if afterwards - "\x1b_Gi=31,s=1,v=1,a=q,t=t;{}\x1b\\", - general_purpose::STANDARD.encode( - temp_file - .path() - .to_str() - .ok_or_else(|| ViuError::Io(Error::other("Could not convert path to &str")))? - ) - ); + write!( + stdout, + // the following are 2 queries, the first "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b" is the *query action* to query kitty graphics support + // followed by the request for the "primary device attributes" "\x1b[c", both are separated by a "\" + // terminals that dont support kitty will only respond to the "primary device attributes" request + // whereas terminals that support kitty, will respond to both actions, specifically we are searching for "_Gi=31" + "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c", + )?; std::io::stdout().flush()?; - // collect Kitty's response after the query - let term = Term::stdout(); let mut response = Vec::new(); - while let Ok(key) = term.read_key() { + // determine if we had the "primary device attributes" reply, as otherwise "c" *could* be part of another query response beforehand + let mut had_pda = false; + + // assign it once instead of having to allocate a vector with static content in each loop + // this sequenece is also called "CSI ? 6" in "Terminal Response" at https://vt100.net/docs/vt510-rm/DA1.html + let pda_seq = Key::UnknownEscSeq(['[', '?', '6'].into()); + + while let Ok(key) = stdin.read_key() { + if key == pda_seq { + had_pda = true; + } + + // The "primary device attributes" response will end with a "c" character + // see "Terminal Response" at https://vt100.net/docs/vt510-rm/DA1.html + // Alternatively, terminate on unknown keys, this could for example happen in cargo test with a `console::Term` read_key, for some reason + let should_break = (had_pda && key == Key::Char('c')) || key == Key::Unknown; + + response.push(key); + + if should_break { + break; + } + } + + // The Graphics query response + let expected = [ + Key::UnknownEscSeq(['_'].into()), + Key::Char('G'), + Key::Char('i'), + Key::Char('='), + Key::Char('3'), + Key::Char('1'), + ]; + + // The Graphics query and the device attributes response could theoretically be in any order + // but most terminals will reply in a FIFO order + if response.len() >= expected.len() && response[..expected.len()] == expected { + return Ok(()); + } + + Err(ViuError::KittyResponse(response)) +} + +/// Close the temporary file that was created, filtering out [`NotFound`](ErrorKind::NotFound) errors. +fn close_tmp_file(temp_file: NamedTempFile) -> ViuResult { + // Explicitly clean up when finished with the file because destructor, OS and Kitty are not deterministic. + if let Err(err) = temp_file.close() { + // Proper Kitty terminals *will delete* the file after fully reading it, if it is in a known temporary directory + // so we dont want to error if the file does not exist anymore + if err.kind() != ErrorKind::NotFound { + return Err(err.into()); + } + } + + Ok(()) +} + +/// Wait for the common "OK" response until returning. +fn wait_for_ok(stdin: &impl ReadKey) -> ViuResult { + let mut response = Vec::new(); + + while let Ok(key) = stdin.read_key() { // The response will end with Esc('x1b'), followed by Backslash('\'). // Also, break if the Unknown key is found, which is returned when we're not in a tty + // https://sw.kovidgoyal.net/kitty/graphics-protocol/#display-images-on-screen let should_break = key == Key::UnknownEscSeq(vec!['\\']) || key == Key::Unknown; response.push(key); if should_break { @@ -102,9 +161,6 @@ fn has_local_support() -> ViuResult { } } - // Explicitly clean up when finished with the file because destructor, OS and Kitty are not deterministic. - temp_file.close()?; - // Kitty response should end with these 3 Keys if it was successful let expected = [ Key::Char('O'), @@ -119,10 +175,39 @@ fn has_local_support() -> ViuResult { Err(ViuError::KittyResponse(response)) } +/// Query the terminal whether it can display an image from a file +fn has_local_support(stdout: &mut impl Write, stdin: &impl ReadKey) -> ViuResult { + // create a temp file that will hold a 1x1 image + let x = image::RgbaImage::new(1, 1); + let raw_img = x.as_raw(); + let temp_file = store_in_tmp_file(raw_img)?; + + // send the query + write!( + stdout, + // t=t tells Kitty it's reading from a temp file and will attempt to delete if afterwards + "\x1b_Gi=31,s=1,v=1,a=q,t=t;{}\x1b\\", + general_purpose::STANDARD.encode( + temp_file + .path() + .to_str() + .ok_or_else(|| ViuError::Io(Error::other("Could not convert path to &str")))? + ) + )?; + std::io::stdout().flush()?; + + wait_for_ok(stdin)?; + + close_tmp_file(temp_file)?; + + Ok(()) +} + // Print with kitty graphics protocol through a temp file // TODO: try with kitty's supported compression fn print_local( stdout: &mut impl Write, + stdin: &impl ReadKey, img: &image::DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { @@ -137,7 +222,7 @@ fn print_local( write!( stdout, - "\x1b_Gf=32,s={},v={},c={},r={},a=T,t=t;{}\x1b\\", + "\x1b_Gf=32,s={},v={},c={},r={},a=T,i=10,t=t;{}\x1b\\", img.width(), img.height(), w, @@ -152,8 +237,9 @@ fn print_local( writeln!(stdout)?; stdout.flush()?; - // Explicitly clean up when finished with the file because destructor, OS and Kitty are not deterministic. - temp_file.close()?; + wait_for_ok(stdin)?; + + close_tmp_file(temp_file)?; Ok((w, h)) } @@ -162,6 +248,7 @@ fn print_local( // TODO: try compression fn print_remote( stdout: &mut impl Write, + stdin: &impl ReadKey, img: &image::DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { @@ -179,7 +266,7 @@ fn print_remote( // write the first chunk, which describes the image write!( stdout, - "\x1b_Gf=32,a=T,t=d,s={},v={},c={},r={},m=1;{}\x1b\\", + "\x1b_Gf=32,a=T,t=d,s={},v={},c={},r={},i=10,m=1;{}\x1b\\", img.width(), img.height(), w, @@ -195,6 +282,9 @@ fn print_remote( } writeln!(stdout)?; stdout.flush()?; + + wait_for_ok(stdin)?; + Ok((w, h)) } @@ -213,6 +303,8 @@ fn store_in_tmp_file(buf: &[u8]) -> std::result::Result #[cfg(test)] mod tests { + use crate::printer::TestKeys; + use super::*; use image::{DynamicImage, GenericImage}; @@ -226,11 +318,30 @@ mod tests { }; let mut vec = Vec::new(); - assert_eq!(print_local(&mut vec, &img, &config).unwrap(), (40, 13)); + + let test_data = [ + Key::UnknownEscSeq(['_'].into()), + Key::Char('G'), + Key::Char('i'), + Key::Char('='), + Key::Char('1'), + Key::Char('0'), + Key::Char(';'), + Key::Char('O'), + Key::Char('K'), + Key::UnknownEscSeq(['\\'].into()), + ]; + let test_response = TestKeys::new(&test_data); + + assert_eq!( + print_local(&mut vec, &test_response, &img, &config).unwrap(), + (40, 13) + ); let result = std::str::from_utf8(&vec).unwrap(); - assert!(result.starts_with("\x1b[4;5H\x1b_Gf=32,s=40,v=25,c=40,r=13,a=T,t=t;")); + assert!(result.starts_with("\x1b[4;5H\x1b_Gf=32,s=40,v=25,c=40,r=13,a=T,i=10,t=t;")); assert!(result.ends_with("\x1b\\\n")); + assert!(test_response.reached_end()); } #[test] @@ -245,12 +356,117 @@ mod tests { }; let mut vec = Vec::new(); - assert_eq!(print_remote(&mut vec, &img, &config).unwrap(), (1, 1)); + + let test_data = [ + Key::UnknownEscSeq(['_'].into()), + Key::Char('G'), + Key::Char('i'), + Key::Char('='), + Key::Char('1'), + Key::Char('0'), + Key::Char(';'), + Key::Char('O'), + Key::Char('K'), + Key::UnknownEscSeq(['\\'].into()), + ]; + let test_response = TestKeys::new(&test_data); + + assert_eq!( + print_remote(&mut vec, &test_response, &img, &config).unwrap(), + (1, 1) + ); let result = std::str::from_utf8(&vec).unwrap(); assert_eq!( result, - "\x1b[6;3H\x1b_Gf=32,a=T,t=d,s=1,v=2,c=1,r=1,m=1;AAAAAAIEBgg=\x1b\\\n" + "\x1b[6;3H\x1b_Gf=32,a=T,t=d,s=1,v=2,c=1,r=1,i=10,m=1;AAAAAAIEBgg=\x1b\\\n" ); + assert!(test_response.reached_end()); + } + + #[test] + fn test_kitty_supported_but_not_remote() { + // test kitty protocol support + let mut stdout = Vec::new(); + + let test_data = [ + Key::UnknownEscSeq(['_'].into()), + Key::Char('G'), + Key::Char('i'), + Key::Char('='), + Key::Char('3'), + Key::Char('1'), + Key::Char(';'), + Key::Char('O'), + Key::Char('K'), + Key::UnknownEscSeq(['\\'].into()), + Key::UnknownEscSeq(['[', '?', '6'].into()), + Key::Char('2'), + Key::Char(';'), + Key::Char('1'), + Key::Char(';'), + Key::Char('4'), + Key::Char('c'), + ]; + let test_response = TestKeys::new(&test_data); + + supports_kitty_protocol(&mut stdout, &test_response).unwrap(); + let result = std::str::from_utf8(&stdout).unwrap(); + + assert_eq!(result, "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c"); + assert!(test_response.reached_end()); + + stdout.clear(); + + // test kitty local protocol support + let mut stdout = Vec::new(); + + let test_data = [ + Key::UnknownEscSeq(['_'].into()), + Key::Char('G'), + Key::Char('i'), + Key::Char('='), + Key::Char('3'), + Key::Char('1'), + Key::Char(';'), + Key::Char('E'), + Key::Char('N'), + Key::Char('O'), + Key::Char('T'), + Key::Char('S'), + Key::Char('U'), + Key::Char('P'), + Key::Char('P'), + Key::Char('O'), + Key::Char('R'), + Key::Char('T'), + Key::Char('E'), + Key::Char('D'), + Key::Char(':'), + Key::UnknownEscSeq(['\\'].into()), + ]; + let test_response = TestKeys::new(&test_data); + + has_local_support(&mut stdout, &test_response).unwrap_err(); + let result = std::str::from_utf8(&stdout).unwrap(); + + assert!(result.starts_with("\x1b_Gi=31,s=1,v=1,a=q,t=t;")); + assert!(result.ends_with("\x1b\\")); + assert!(test_response.reached_end()); + } + + #[test] + fn test_no_kitty_support() { + let mut stdout = Vec::new(); + + // only the "primary device attributes" + let test_data = [Key::UnknownEscSeq(['[', '?', '6'].into()), Key::Char('c')]; + let test_response = TestKeys::new(&test_data); + + supports_kitty_protocol(&mut stdout, &test_response).unwrap_err(); + let result = std::str::from_utf8(&stdout).unwrap(); + + assert_eq!(result, "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c"); + assert!(test_response.reached_end()); } } diff --git a/src/printer/mod.rs b/src/printer/mod.rs index 3bb6544..28eadf4 100644 --- a/src/printer/mod.rs +++ b/src/printer/mod.rs @@ -1,6 +1,7 @@ use crate::config::Config; use crate::error::{ViuError, ViuResult}; use crate::utils::terminal_size; +use console::{Key, Term}; use crossterm::cursor::{MoveRight, MoveTo, MoveToPreviousLine}; use crossterm::execute; use image::{DynamicImage, GenericImageView}; @@ -33,6 +34,65 @@ pub use self::sixel_util::is_sixel_supported; mod iterm; pub use iterm::iTermPrinter; pub use iterm::is_iterm_supported; +#[cfg(test)] +pub(crate) use test_utils::TestKeys; + +/// Trait to allow reading keys from multiple inputs like [`console::Term`] or a custom Testing utility. +pub(crate) trait ReadKey { + fn read_key(&self) -> std::io::Result; +} + +impl ReadKey for Term { + fn read_key(&self) -> std::io::Result { + self.read_key() + } +} + +#[cfg(test)] +mod test_utils { + use std::{cell::RefCell, io}; + + use console::Key; + + use crate::printer::ReadKey; + + /// Test Utility to replay key sequences like otherwise gotten from a normal console via [`console::Term`]. + #[derive(Debug, Clone)] + pub(crate) struct TestKeys<'a> { + data: &'a [Key], + next_idx: RefCell, + } + + impl<'a> TestKeys<'a> { + pub fn new(data: &'a [Key]) -> Self { + Self { + data, + next_idx: RefCell::new(0), + } + } + + /// Test if all the data in this instance has been replayed exactly. + pub fn reached_end(&self) -> bool { + *self.next_idx.borrow() == self.data.len() + } + } + + impl ReadKey for TestKeys<'_> { + fn read_key(&self) -> io::Result { + let idx = *self.next_idx.borrow(); + + if idx >= self.data.len() { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "Reached the end of the data", + )); + } + + *self.next_idx.borrow_mut() += 1; + Ok(self.data[idx].clone()) + } + } +} pub trait Printer { // Print the given image in the terminal while respecting the options in the config struct. @@ -40,6 +100,7 @@ pub trait Printer { fn print( &self, stdout: &mut impl Write, + stdin: &impl ReadKey, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)>; @@ -48,17 +109,19 @@ pub trait Printer { fn print_from_file>( &self, stdout: &mut impl Write, + stdin: &impl ReadKey, filename: P, config: &Config, ) -> ViuResult<(u32, u32)> { let img = image::ImageReader::open(filename)? .with_guessed_format()? .decode()?; - self.print(stdout, &img, config) + self.print(stdout, stdin, &img, config) } } #[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy)] pub enum PrinterType { Block, Kitty, @@ -73,17 +136,18 @@ impl Printer for PrinterType { fn print( &self, stdout: &mut impl Write, + stdin: &impl ReadKey, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> { match self { - PrinterType::Block => BlockPrinter.print(stdout, img, config), - PrinterType::Kitty => KittyPrinter.print(stdout, img, config), - PrinterType::iTerm => iTermPrinter.print(stdout, img, config), + PrinterType::Block => BlockPrinter.print(stdout, stdin, img, config), + PrinterType::Kitty => KittyPrinter.print(stdout, stdin, img, config), + PrinterType::iTerm => iTermPrinter.print(stdout, stdin, img, config), #[cfg(feature = "sixel")] - PrinterType::Sixel => SixelPrinter.print(stdout, img, config), + PrinterType::Sixel => SixelPrinter.print(stdout, stdin, img, config), #[cfg(feature = "icy_sixel")] - PrinterType::IcySixel => IcySixelPrinter.print(stdout, img, config), + PrinterType::IcySixel => IcySixelPrinter.print(stdout, stdin, img, config), } } @@ -91,17 +155,20 @@ impl Printer for PrinterType { fn print_from_file>( &self, stdout: &mut impl Write, + stdin: &impl ReadKey, filename: P, config: &Config, ) -> ViuResult<(u32, u32)> { match self { - PrinterType::Block => BlockPrinter.print_from_file(stdout, filename, config), - PrinterType::Kitty => KittyPrinter.print_from_file(stdout, filename, config), - PrinterType::iTerm => iTermPrinter.print_from_file(stdout, filename, config), + PrinterType::Block => BlockPrinter.print_from_file(stdout, stdin, filename, config), + PrinterType::Kitty => KittyPrinter.print_from_file(stdout, stdin, filename, config), + PrinterType::iTerm => iTermPrinter.print_from_file(stdout, stdin, filename, config), #[cfg(feature = "sixel")] - PrinterType::Sixel => SixelPrinter.print_from_file(stdout, filename, config), + PrinterType::Sixel => SixelPrinter.print_from_file(stdout, stdin, filename, config), #[cfg(feature = "icy_sixel")] - PrinterType::IcySixel => IcySixelPrinter.print_from_file(stdout, filename, config), + PrinterType::IcySixel => { + IcySixelPrinter.print_from_file(stdout, stdin, filename, config) + } } } } diff --git a/src/printer/sixel.rs b/src/printer/sixel.rs index 9ad1d12..333c0d0 100644 --- a/src/printer/sixel.rs +++ b/src/printer/sixel.rs @@ -1,17 +1,19 @@ use crate::error::ViuResult; -use crate::printer::{adjust_offset, find_best_fit, Printer}; +use crate::printer::{adjust_offset, find_best_fit, Printer, ReadKey}; use crate::Config; use image::{imageops::FilterType, DynamicImage, GenericImageView}; use sixel_rs::encoder::{Encoder, QuickFrameBuilder}; use sixel_rs::optflags::EncodePolicy; use std::io::Write; +#[derive(Debug, Clone)] pub struct SixelPrinter; impl Printer for SixelPrinter { fn print( &self, stdout: &mut impl Write, + _stdin: &impl ReadKey, img: &DynamicImage, config: &Config, ) -> ViuResult<(u32, u32)> {