diff --git a/Cargo.lock b/Cargo.lock index 18db642fe..d849d8d49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -741,11 +741,11 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "enable-ansi-support" -version = "0.3.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea7457668b3da8a4b702f3d79e131aa3e81cd7e81cc95fb2d54fce9f182ecc77" +checksum = "aa4ff3ae2a9aa54bf7ee0983e59303224de742818c1822d89f07da9856d9bc60" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.42.0", ] [[package]] @@ -2616,9 +2616,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -2970,7 +2970,7 @@ dependencies = [ "clap", "color_quant", "image", - "libc", + "rustix", ] [[package]] @@ -3563,9 +3563,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", @@ -4473,7 +4473,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.1.3", + "windows-link", "windows-result", "windows-strings", ] @@ -4506,19 +4506,13 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - [[package]] name = "windows-result" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -4527,7 +4521,22 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link 0.1.3", + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -4557,15 +4566,6 @@ dependencies = [ "windows-targets 0.53.5", ] -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -4614,6 +4614,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4632,6 +4638,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4650,6 +4662,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4680,6 +4698,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4698,6 +4722,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4716,6 +4746,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4734,6 +4770,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/image/Cargo.toml b/image/Cargo.toml index efaa469e5..ff2791c9f 100644 --- a/image/Cargo.toml +++ b/image/Cargo.toml @@ -15,4 +15,4 @@ image.workspace = true [target.'cfg(not(windows))'.dependencies] color_quant = "1.1.0" base64 = "0.22.1" -libc = "0.2.177" +rustix = { version = "1.1.2", features = ["termios", "event"] } diff --git a/image/src/iterm.rs b/image/src/iterm.rs index 087111d8b..ea4f7fe83 100644 --- a/image/src/iterm.rs +++ b/image/src/iterm.rs @@ -1,29 +1,19 @@ -use crate::get_dimensions; use anyhow::Result; use base64::{engine, Engine}; use image::{imageops::FilterType, DynamicImage}; +use rustix::termios::tcgetwinsize; use std::env; use std::io::Cursor; -pub struct ITermBackend {} +pub struct ITermBackend; impl ITermBackend { - pub fn new() -> Self { - ITermBackend {} - } - pub fn supported() -> bool { let term_program = env::var("TERM_PROGRAM").unwrap_or_else(|_| "".to_string()); term_program == "iTerm.app" } } -impl Default for ITermBackend { - fn default() -> Self { - Self::new() - } -} - impl super::ImageBackend for ITermBackend { fn add_image( &self, @@ -31,7 +21,7 @@ impl super::ImageBackend for ITermBackend { image: &DynamicImage, _colors: usize, ) -> Result { - let tty_size = unsafe { get_dimensions() }; + let tty_size = tcgetwinsize(std::io::stdin())?; let width_ratio = f64::from(tty_size.ws_col) / f64::from(tty_size.ws_xpixel); let height_ratio = f64::from(tty_size.ws_row) / f64::from(tty_size.ws_ypixel); diff --git a/image/src/kitty.rs b/image/src/kitty.rs index 875f8e2ff..237add6cf 100644 --- a/image/src/kitty.rs +++ b/image/src/kitty.rs @@ -1,32 +1,30 @@ -use crate::get_dimensions; -use anyhow::Result; +use anyhow::{Context as _, Result}; use base64::{engine, Engine}; use image::{imageops::FilterType, DynamicImage}; -use libc::{ - c_void, poll, pollfd, read, tcgetattr, tcsetattr, termios, ECHO, ICANON, POLLIN, STDIN_FILENO, - TCSANOW, -}; + +use rustix::event::{poll, PollFd, PollFlags, Timespec}; +use rustix::io::read; +use rustix::termios::{tcgetattr, tcgetwinsize, tcsetattr, LocalModes, OptionalActions}; + use std::io::{stdout, Write}; +use std::os::fd::AsFd as _; use std::time::Instant; -pub struct KittyBackend {} +pub struct KittyBackend; impl KittyBackend { - pub fn new() -> Self { - Self {} - } - - pub fn supported() -> bool { + pub fn supported() -> Result { + let stdin = std::io::stdin(); // save terminal attributes and disable canonical input processing mode - let old_attributes = unsafe { - let mut old_attributes: termios = std::mem::zeroed(); - tcgetattr(STDIN_FILENO, &mut old_attributes); + let old_attributes = { + let old = tcgetattr(&stdin).context("Failed to recieve terminal attibutes")?; - let mut new_attributes = old_attributes; - new_attributes.c_lflag &= !ICANON; - new_attributes.c_lflag &= !ECHO; - tcsetattr(STDIN_FILENO, TCSANOW, &new_attributes); - old_attributes + let mut new = old.clone(); + new.local_modes &= !LocalModes::ICANON; + new.local_modes &= !LocalModes::ECHO; + tcsetattr(&stdin, OptionalActions::Now, &new) + .context("Failed to update terminal attributes")?; + old }; // generate red rgba test image @@ -38,49 +36,36 @@ impl KittyBackend { "\x1B_Gi=1,f=32,s=32,v=32,a=q;{}\x1B\\", engine::general_purpose::STANDARD.encode(&test_image) ); - stdout().flush().unwrap(); + stdout().flush()?; let start_time = Instant::now(); - let mut stdin_pollfd = pollfd { - fd: STDIN_FILENO, - events: POLLIN, - revents: 0, - }; + let stdin_fd = stdin.as_fd(); + let mut stdin_pollfd = [PollFd::new(&stdin_fd, PollFlags::IN)]; let allowed_bytes = [0x1B, b'_', b'G', b'\\']; let mut buf = Vec::::new(); loop { // check for timeout while polling to avoid blocking the main thread - while unsafe { poll(&mut stdin_pollfd, 1, 0) < 1 } { + while poll(&mut stdin_pollfd, Some(&Timespec::default()))? < 1 { if start_time.elapsed().as_millis() > 50 { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return false; + tcsetattr(&stdin, OptionalActions::Now, &old_attributes) + .context("Failed to update terminal attributes")?; + return Ok(false); } } - let mut byte = 0; - unsafe { - read(STDIN_FILENO, &mut byte as *mut _ as *mut c_void, 1); - } - if allowed_bytes.contains(&byte) { - buf.push(byte); + let mut byte = [0]; + read(&stdin, &mut byte)?; + if allowed_bytes.contains(&byte[0]) { + buf.push(byte[0]); } if buf.starts_with(&[0x1B, b'_', b'G']) && buf.ends_with(&[0x1B, b'\\']) { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return true; + tcsetattr(&stdin, OptionalActions::Now, &old_attributes) + .context("Failed to update terminal attributes")?; + return Ok(true); } } } } -impl Default for KittyBackend { - fn default() -> Self { - Self::new() - } -} - impl super::ImageBackend for KittyBackend { fn add_image( &self, @@ -88,7 +73,7 @@ impl super::ImageBackend for KittyBackend { image: &DynamicImage, _colors: usize, ) -> Result { - let tty_size = unsafe { get_dimensions() }; + let tty_size = tcgetwinsize(std::io::stdin())?; let width_ratio = f64::from(tty_size.ws_col) / f64::from(tty_size.ws_xpixel); let height_ratio = f64::from(tty_size.ws_row) / f64::from(tty_size.ws_ypixel); diff --git a/image/src/lib.rs b/image/src/lib.rs index fe48a5981..cbeea8ecb 100644 --- a/image/src/lib.rs +++ b/image/src/lib.rs @@ -19,47 +19,32 @@ pub trait ImageBackend { fn add_image(&self, lines: Vec, image: &DynamicImage, colors: usize) -> Result; } -pub fn get_best_backend() -> Option> { +pub fn get_best_backend() -> Result>> { #[cfg(not(windows))] - if sixel::SixelBackend::supported() { - Some(Box::new(sixel::SixelBackend::new())) - } else if kitty::KittyBackend::supported() { - Some(Box::new(kitty::KittyBackend::new())) + if sixel::SixelBackend::supported()? { + Ok(Some(Box::new(sixel::SixelBackend))) + } else if kitty::KittyBackend::supported()? { + Ok(Some(Box::new(kitty::KittyBackend))) } else if iterm::ITermBackend::supported() { - Some(Box::new(iterm::ITermBackend::new())) + Ok(Some(Box::new(iterm::ITermBackend))) } else { - None + Ok(None) } #[cfg(windows)] - None + Ok(None) } #[allow(unused_variables)] pub fn get_image_backend(image_protocol: ImageProtocol) -> Option> { #[cfg(not(windows))] let backend = Some(match image_protocol { - ImageProtocol::Kitty => Box::new(kitty::KittyBackend::new()) as Box, - ImageProtocol::Iterm => Box::new(iterm::ITermBackend::new()) as Box, - ImageProtocol::Sixel => Box::new(sixel::SixelBackend::new()) as Box, + ImageProtocol::Kitty => Box::new(kitty::KittyBackend) as Box, + ImageProtocol::Iterm => Box::new(iterm::ITermBackend) as Box, + ImageProtocol::Sixel => Box::new(sixel::SixelBackend) as Box, }); #[cfg(windows)] let backend = None; backend } - -#[cfg(not(windows))] -unsafe fn get_dimensions() -> libc::winsize { - use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ}; - use std::mem::zeroed; - - let mut window: winsize = zeroed(); - let result = ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut window); - - if result == -1 { - zeroed() - } else { - window - } -} diff --git a/image/src/sixel.rs b/image/src/sixel.rs index c7dd75069..0d586f8a0 100644 --- a/image/src/sixel.rs +++ b/image/src/sixel.rs @@ -1,70 +1,61 @@ -use crate::get_dimensions; -use anyhow::{Context, Result}; +use anyhow::{Context as _, Result}; use color_quant::NeuQuant; use image::{ imageops::{colorops, FilterType}, DynamicImage, GenericImageView, ImageBuffer, Pixel, Rgb, }; -use libc::{ - c_void, poll, pollfd, read, tcgetattr, tcsetattr, termios, ECHO, ICANON, POLLIN, STDIN_FILENO, - TCSANOW, -}; + +use rustix::event::{poll, PollFd, PollFlags, Timespec}; +use rustix::io::read; +use rustix::termios::{tcgetattr, tcgetwinsize, tcsetattr, LocalModes, OptionalActions}; + use std::io::{stdout, Write}; +use std::os::fd::AsFd; use std::time::Instant; -pub struct SixelBackend {} +pub struct SixelBackend; impl SixelBackend { - pub fn new() -> Self { - Self {} - } - - pub fn supported() -> bool { + pub fn supported() -> Result { + let stdin = std::io::stdin(); // save terminal attributes and disable canonical input processing mode - let old_attributes = unsafe { - let mut old_attributes: termios = std::mem::zeroed(); - tcgetattr(STDIN_FILENO, &mut old_attributes); + let old_attributes = { + let old = tcgetattr(&stdin).context("Failed to recieve terminal attibutes")?; - let mut new_attributes = old_attributes; - new_attributes.c_lflag &= !ICANON; - new_attributes.c_lflag &= !ECHO; - tcsetattr(STDIN_FILENO, TCSANOW, &new_attributes); - old_attributes + let mut new = old.clone(); + new.local_modes &= !LocalModes::ICANON; + new.local_modes &= !LocalModes::ECHO; + tcsetattr(&stdin, OptionalActions::Now, &new) + .context("Failed to update terminal attributes")?; + old }; // ask for the primary device attribute string print!("\x1B[c"); - stdout().flush().unwrap(); + stdout().flush()?; let start_time = Instant::now(); - let mut stdin_pollfd = pollfd { - fd: STDIN_FILENO, - events: POLLIN, - revents: 0, - }; + let stdin_fd = stdin.as_fd(); + let mut stdin_pollfd = [PollFd::new(&stdin_fd, PollFlags::IN)]; let mut buf = Vec::::new(); loop { // check for timeout while polling to avoid blocking the main thread - while unsafe { poll(&mut stdin_pollfd, 1, 0) < 1 } { + while poll(&mut stdin_pollfd, Some(&Timespec::default()))? < 1 { if start_time.elapsed().as_millis() > 50 { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return false; + tcsetattr(stdin, OptionalActions::Now, &old_attributes) + .context("Failed to update terminal attributes")?; + return Ok(false); } } - let mut byte = 0; - unsafe { - read(STDIN_FILENO, &mut byte as *mut _ as *mut c_void, 1); - } - buf.push(byte); + let mut byte = [0]; + read(&stdin, &mut byte)?; + buf.push(byte[0]); if buf.starts_with(&[0x1B, b'[', b'?']) && buf.ends_with(b"c") { for attribute in buf[3..(buf.len() - 1)].split(|x| *x == b';') { if attribute == [b'4'] { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return true; + tcsetattr(stdin, OptionalActions::Now, &old_attributes) + .context("Failed to update terminal attributes")?; + return Ok(true); } } } @@ -72,16 +63,10 @@ impl SixelBackend { } } -impl Default for SixelBackend { - fn default() -> Self { - Self::new() - } -} - impl super::ImageBackend for SixelBackend { #[allow(clippy::map_entry)] fn add_image(&self, lines: Vec, image: &DynamicImage, colors: usize) -> Result { - let tty_size = unsafe { get_dimensions() }; + let tty_size = tcgetwinsize(std::io::stdin())?; let cw = tty_size.ws_xpixel / tty_size.ws_col; let lh = tty_size.ws_ypixel / tty_size.ws_row; let width_ratio = 1.0 / cw as f64; diff --git a/src/ui/printer/factory.rs b/src/ui/printer/factory.rs index acf549cfe..ddab896e7 100644 --- a/src/ui/printer/factory.rs +++ b/src/ui/printer/factory.rs @@ -34,8 +34,8 @@ impl PrinterFactory { .image .image_protocol .map_or_else(onefetch_image::get_best_backend, |s| { - onefetch_image::get_image_backend(s) - }) + Ok(onefetch_image::get_image_backend(s)) + })? } else { None };