From 43ca000d151615140a4851df70075393d58b2cb5 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Wed, 26 Nov 2025 19:24:32 +0000 Subject: [PATCH 1/8] chore: better struct declarations --- image/src/iterm.rs | 12 +----------- image/src/kitty.rs | 12 +----------- image/src/lib.rs | 12 ++++++------ image/src/sixel.rs | 12 +----------- 4 files changed, 9 insertions(+), 39 deletions(-) diff --git a/image/src/iterm.rs b/image/src/iterm.rs index 087111d8b..cca369d34 100644 --- a/image/src/iterm.rs +++ b/image/src/iterm.rs @@ -5,25 +5,15 @@ use image::{imageops::FilterType, DynamicImage}; 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, diff --git a/image/src/kitty.rs b/image/src/kitty.rs index 875f8e2ff..47829e563 100644 --- a/image/src/kitty.rs +++ b/image/src/kitty.rs @@ -9,13 +9,9 @@ use libc::{ use std::io::{stdout, Write}; use std::time::Instant; -pub struct KittyBackend {} +pub struct KittyBackend; impl KittyBackend { - pub fn new() -> Self { - Self {} - } - pub fn supported() -> bool { // save terminal attributes and disable canonical input processing mode let old_attributes = unsafe { @@ -75,12 +71,6 @@ impl KittyBackend { } } -impl Default for KittyBackend { - fn default() -> Self { - Self::new() - } -} - impl super::ImageBackend for KittyBackend { fn add_image( &self, diff --git a/image/src/lib.rs b/image/src/lib.rs index fe48a5981..75fef35d7 100644 --- a/image/src/lib.rs +++ b/image/src/lib.rs @@ -22,11 +22,11 @@ pub trait ImageBackend { pub fn get_best_backend() -> Option> { #[cfg(not(windows))] if sixel::SixelBackend::supported() { - Some(Box::new(sixel::SixelBackend::new())) + Some(Box::new(sixel::SixelBackend)) } else if kitty::KittyBackend::supported() { - Some(Box::new(kitty::KittyBackend::new())) + Some(Box::new(kitty::KittyBackend)) } else if iterm::ITermBackend::supported() { - Some(Box::new(iterm::ITermBackend::new())) + Some(Box::new(iterm::ITermBackend)) } else { None } @@ -39,9 +39,9 @@ pub fn get_best_backend() -> Option> { 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)] diff --git a/image/src/sixel.rs b/image/src/sixel.rs index c7dd75069..c97efa72d 100644 --- a/image/src/sixel.rs +++ b/image/src/sixel.rs @@ -12,13 +12,9 @@ use libc::{ use std::io::{stdout, Write}; use std::time::Instant; -pub struct SixelBackend {} +pub struct SixelBackend; impl SixelBackend { - pub fn new() -> Self { - Self {} - } - pub fn supported() -> bool { // save terminal attributes and disable canonical input processing mode let old_attributes = unsafe { @@ -72,12 +68,6 @@ 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 { From 88320f52b094c6ef6a715e5027af7d02d79fd044 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Thu, 27 Nov 2025 19:03:23 +0000 Subject: [PATCH 2/8] add `nix` dependency --- Cargo.lock | 101 ++++++++++++++++++++++++++++++++++++----------- image/Cargo.toml | 1 + 2 files changed, 79 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62dda2a4a..5b40d9f3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,7 +372,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -728,11 +728,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]] @@ -2663,6 +2663,18 @@ dependencies = [ "pxfm", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "npm-package-json" version = "0.1.3" @@ -2777,6 +2789,7 @@ dependencies = [ "color_quant", "image", "libc", + "nix", ] [[package]] @@ -4285,7 +4298,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.1.3", + "windows-link", "windows-result", "windows-strings", ] @@ -4318,19 +4331,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]] @@ -4339,7 +4346,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]] @@ -4378,15 +4400,6 @@ dependencies = [ "windows-targets 0.53.3", ] -[[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" @@ -4424,7 +4437,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link 0.1.3", + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4435,6 +4448,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[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" @@ -4453,6 +4472,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" @@ -4471,6 +4496,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" @@ -4501,6 +4532,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" @@ -4519,6 +4556,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" @@ -4537,6 +4580,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" @@ -4555,6 +4604,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..b4b98d47a 100644 --- a/image/Cargo.toml +++ b/image/Cargo.toml @@ -15,4 +15,5 @@ image.workspace = true [target.'cfg(not(windows))'.dependencies] color_quant = "1.1.0" base64 = "0.22.1" +nix = { version = "0.30.1", features = ["poll", "term"] } libc = "0.2.177" From de1778fb09db8c8a0b56df58d9e2603a7e02d603 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Thu, 27 Nov 2025 19:24:47 +0000 Subject: [PATCH 3/8] feat: safe `kitty` backend support checker --- image/src/kitty.rs | 65 ++++++++++++++++++--------------------- image/src/lib.rs | 14 ++++----- src/ui/printer/factory.rs | 4 +-- 3 files changed, 39 insertions(+), 44 deletions(-) diff --git a/image/src/kitty.rs b/image/src/kitty.rs index 47829e563..31804b89d 100644 --- a/image/src/kitty.rs +++ b/image/src/kitty.rs @@ -1,28 +1,31 @@ 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 nix::poll::{poll, PollFd, PollFlags, PollTimeout}; +use nix::sys::termios::{tcgetattr, tcsetattr, LocalFlags, SetArg}; +use nix::unistd::read; + use std::io::{stdout, Write}; +use std::os::fd::AsFd as _; use std::time::Instant; pub struct KittyBackend; impl KittyBackend { - 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_flags &= !LocalFlags::ICANON; + new.local_flags &= !LocalFlags::ECHO; + tcsetattr(&stdin, SetArg::TCSANOW, &new) + .context("Failed to update terminal attributes")?; + old }; // generate red rgba test image @@ -34,38 +37,30 @@ 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 mut stdin_pollfd = [PollFd::new(stdin.as_fd(), PollFlags::POLLIN)]; 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, PollTimeout::ZERO)? < 1 { if start_time.elapsed().as_millis() > 50 { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return false; + tcsetattr(&stdin, SetArg::TCSANOW, &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, SetArg::TCSANOW, &old_attributes) + .context("Failed to update terminal attributes")?; + return Ok(true); } } } diff --git a/image/src/lib.rs b/image/src/lib.rs index 75fef35d7..d80d8001c 100644 --- a/image/src/lib.rs +++ b/image/src/lib.rs @@ -19,20 +19,20 @@ 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)) - } else if kitty::KittyBackend::supported() { - Some(Box::new(kitty::KittyBackend)) + 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)) + Ok(Some(Box::new(iterm::ITermBackend))) } else { - None + Ok(None) } #[cfg(windows)] - None + Ok(None) } #[allow(unused_variables)] 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 }; From df5eeeb810767c005b962f69775f66e145651911 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Thu, 27 Nov 2025 19:28:17 +0000 Subject: [PATCH 4/8] feat: safe `sixel` backend support checker --- image/src/lib.rs | 2 +- image/src/sixel.rs | 63 +++++++++++++++++++++------------------------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/image/src/lib.rs b/image/src/lib.rs index d80d8001c..7748b6501 100644 --- a/image/src/lib.rs +++ b/image/src/lib.rs @@ -21,7 +21,7 @@ pub trait ImageBackend { pub fn get_best_backend() -> Result>> { #[cfg(not(windows))] - if sixel::SixelBackend::supported() { + if sixel::SixelBackend::supported()? { Ok(Some(Box::new(sixel::SixelBackend))) } else if kitty::KittyBackend::supported()? { Ok(Some(Box::new(kitty::KittyBackend))) diff --git a/image/src/sixel.rs b/image/src/sixel.rs index c97efa72d..1a8343e25 100644 --- a/image/src/sixel.rs +++ b/image/src/sixel.rs @@ -1,66 +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 nix::poll::{poll, PollFd, PollFlags, PollTimeout}; +use nix::sys::termios::{tcgetattr, tcsetattr, LocalFlags, SetArg}; +use nix::unistd::read; + use std::io::{stdout, Write}; +use std::os::fd::AsFd; use std::time::Instant; pub struct SixelBackend; impl SixelBackend { - 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_flags &= !LocalFlags::ICANON; + new.local_flags &= !LocalFlags::ECHO; + tcsetattr(&stdin, SetArg::TCSANOW, &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 mut stdin_pollfd = [PollFd::new(stdin.as_fd(), PollFlags::POLLIN)]; 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, PollTimeout::ZERO)? < 1 { if start_time.elapsed().as_millis() > 50 { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return false; + tcsetattr(stdin, SetArg::TCSANOW, &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, SetArg::TCSANOW, &old_attributes) + .context("Failed to update terminal attributes")?; + return Ok(true); } } } From abb7d9a84555966f3a80a76c373e64dbfe91c41e Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Thu, 27 Nov 2025 20:06:26 +0000 Subject: [PATCH 5/8] feat: safe `get_dimensions` (??) --- Cargo.lock | 6 +++--- image/Cargo.toml | 2 +- image/src/iterm.rs | 2 +- image/src/kitty.rs | 2 +- image/src/lib.rs | 24 ++++++++++++++++-------- image/src/sixel.rs | 2 +- 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b40d9f3a..751625a62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3792,12 +3792,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/image/Cargo.toml b/image/Cargo.toml index b4b98d47a..997541bf1 100644 --- a/image/Cargo.toml +++ b/image/Cargo.toml @@ -15,5 +15,5 @@ image.workspace = true [target.'cfg(not(windows))'.dependencies] color_quant = "1.1.0" base64 = "0.22.1" -nix = { version = "0.30.1", features = ["poll", "term"] } +nix = { version = "0.30.1", features = ["poll", "term", "ioctl"] } libc = "0.2.177" diff --git a/image/src/iterm.rs b/image/src/iterm.rs index cca369d34..3d57f0e17 100644 --- a/image/src/iterm.rs +++ b/image/src/iterm.rs @@ -21,7 +21,7 @@ impl super::ImageBackend for ITermBackend { image: &DynamicImage, _colors: usize, ) -> Result { - let tty_size = unsafe { get_dimensions() }; + let tty_size = get_dimensions()?; 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 31804b89d..1689ac289 100644 --- a/image/src/kitty.rs +++ b/image/src/kitty.rs @@ -73,7 +73,7 @@ impl super::ImageBackend for KittyBackend { image: &DynamicImage, _colors: usize, ) -> Result { - let tty_size = unsafe { get_dimensions() }; + let tty_size = get_dimensions()?; 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 7748b6501..bab5c299f 100644 --- a/image/src/lib.rs +++ b/image/src/lib.rs @@ -1,5 +1,6 @@ -use anyhow::Result; +use anyhow::{bail, Result}; use image::DynamicImage; +use nix::pty::Winsize; #[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)] pub enum ImageProtocol { @@ -50,16 +51,23 @@ pub fn get_image_backend(image_protocol: ImageProtocol) -> Option libc::winsize { - use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ}; - use std::mem::zeroed; +fn get_dimensions() -> Result { + nix::ioctl_read_bad!(ioctl, nix::libc::TIOCGWINSZ, nix::libc::winsize); - let mut window: winsize = zeroed(); - let result = ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut window); + let mut window = Winsize { + ws_col: 0, + ws_row: 0, + ws_xpixel: 0, + ws_ypixel: 0, + }; + let result = unsafe { + use std::os::fd::AsRawFd as _; + ioctl(std::io::stdout().as_raw_fd(), &mut window)? + }; if result == -1 { - zeroed() + bail!("ioctl error!") } else { - window + Ok(window) } } diff --git a/image/src/sixel.rs b/image/src/sixel.rs index 1a8343e25..8a8af6fcd 100644 --- a/image/src/sixel.rs +++ b/image/src/sixel.rs @@ -66,7 +66,7 @@ impl SixelBackend { 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 = get_dimensions()?; 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; From 6dacb42b37c1d13e44150336b5b6e8d0d76e5709 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Thu, 27 Nov 2025 20:09:47 +0000 Subject: [PATCH 6/8] chore: remove `libc` --- Cargo.lock | 1 - image/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 751625a62..2fd8b72f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2788,7 +2788,6 @@ dependencies = [ "clap", "color_quant", "image", - "libc", "nix", ] diff --git a/image/Cargo.toml b/image/Cargo.toml index 997541bf1..2a0e41b52 100644 --- a/image/Cargo.toml +++ b/image/Cargo.toml @@ -16,4 +16,3 @@ image.workspace = true color_quant = "1.1.0" base64 = "0.22.1" nix = { version = "0.30.1", features = ["poll", "term", "ioctl"] } -libc = "0.2.177" From c7f807557410b97dff86a50d2621329c4143651d Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Fri, 28 Nov 2025 19:00:13 +0000 Subject: [PATCH 7/8] feat: replace `get_dimensions` with `tcgetwinsize` --- Cargo.lock | 9 +++++---- image/Cargo.toml | 1 + image/src/iterm.rs | 4 ++-- image/src/kitty.rs | 4 ++-- image/src/lib.rs | 26 ++------------------------ image/src/sixel.rs | 4 ++-- 6 files changed, 14 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fd8b72f0..44c43a3a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2591,9 +2591,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" @@ -2789,6 +2789,7 @@ dependencies = [ "color_quant", "image", "nix", + "rustix", ] [[package]] @@ -3400,9 +3401,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", diff --git a/image/Cargo.toml b/image/Cargo.toml index 2a0e41b52..37edc68ae 100644 --- a/image/Cargo.toml +++ b/image/Cargo.toml @@ -16,3 +16,4 @@ image.workspace = true color_quant = "1.1.0" base64 = "0.22.1" nix = { version = "0.30.1", features = ["poll", "term", "ioctl"] } +rustix = { version = "1.1.2", features = ["termios"] } diff --git a/image/src/iterm.rs b/image/src/iterm.rs index 3d57f0e17..ea4f7fe83 100644 --- a/image/src/iterm.rs +++ b/image/src/iterm.rs @@ -1,7 +1,7 @@ -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; @@ -21,7 +21,7 @@ impl super::ImageBackend for ITermBackend { image: &DynamicImage, _colors: usize, ) -> Result { - let tty_size = 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 1689ac289..58c4ba9a9 100644 --- a/image/src/kitty.rs +++ b/image/src/kitty.rs @@ -1,4 +1,3 @@ -use crate::get_dimensions; use anyhow::{Context as _, Result}; use base64::{engine, Engine}; use image::{imageops::FilterType, DynamicImage}; @@ -6,6 +5,7 @@ use image::{imageops::FilterType, DynamicImage}; use nix::poll::{poll, PollFd, PollFlags, PollTimeout}; use nix::sys::termios::{tcgetattr, tcsetattr, LocalFlags, SetArg}; use nix::unistd::read; +use rustix::termios::tcgetwinsize; use std::io::{stdout, Write}; use std::os::fd::AsFd as _; @@ -73,7 +73,7 @@ impl super::ImageBackend for KittyBackend { image: &DynamicImage, _colors: usize, ) -> Result { - let tty_size = 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 bab5c299f..abf446ba3 100644 --- a/image/src/lib.rs +++ b/image/src/lib.rs @@ -1,6 +1,6 @@ -use anyhow::{bail, Result}; +use anyhow::Result; use image::DynamicImage; -use nix::pty::Winsize; + #[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)] pub enum ImageProtocol { @@ -49,25 +49,3 @@ pub fn get_image_backend(image_protocol: ImageProtocol) -> Option Result { - nix::ioctl_read_bad!(ioctl, nix::libc::TIOCGWINSZ, nix::libc::winsize); - - let mut window = Winsize { - ws_col: 0, - ws_row: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; - let result = unsafe { - use std::os::fd::AsRawFd as _; - ioctl(std::io::stdout().as_raw_fd(), &mut window)? - }; - - if result == -1 { - bail!("ioctl error!") - } else { - Ok(window) - } -} diff --git a/image/src/sixel.rs b/image/src/sixel.rs index 8a8af6fcd..caa3f5cf5 100644 --- a/image/src/sixel.rs +++ b/image/src/sixel.rs @@ -1,4 +1,3 @@ -use crate::get_dimensions; use anyhow::{Context as _, Result}; use color_quant::NeuQuant; use image::{ @@ -9,6 +8,7 @@ use image::{ use nix::poll::{poll, PollFd, PollFlags, PollTimeout}; use nix::sys::termios::{tcgetattr, tcsetattr, LocalFlags, SetArg}; use nix::unistd::read; +use rustix::termios::tcgetwinsize; use std::io::{stdout, Write}; use std::os::fd::AsFd; @@ -66,7 +66,7 @@ impl SixelBackend { impl super::ImageBackend for SixelBackend { #[allow(clippy::map_entry)] fn add_image(&self, lines: Vec, image: &DynamicImage, colors: usize) -> Result { - let tty_size = 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; From dd0bc8973ab1c2cecaa9f1ce365fb37591e6c292 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Fri, 28 Nov 2025 20:17:41 +0000 Subject: [PATCH 8/8] chore: move to `rustix` --- Cargo.lock | 13 ------------- image/Cargo.toml | 3 +-- image/src/kitty.rs | 22 +++++++++++----------- image/src/lib.rs | 1 - image/src/sixel.rs | 22 +++++++++++----------- 5 files changed, 23 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44c43a3a7..041d882f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2663,18 +2663,6 @@ dependencies = [ "pxfm", ] -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "npm-package-json" version = "0.1.3" @@ -2788,7 +2776,6 @@ dependencies = [ "clap", "color_quant", "image", - "nix", "rustix", ] diff --git a/image/Cargo.toml b/image/Cargo.toml index 37edc68ae..ff2791c9f 100644 --- a/image/Cargo.toml +++ b/image/Cargo.toml @@ -15,5 +15,4 @@ image.workspace = true [target.'cfg(not(windows))'.dependencies] color_quant = "1.1.0" base64 = "0.22.1" -nix = { version = "0.30.1", features = ["poll", "term", "ioctl"] } -rustix = { version = "1.1.2", features = ["termios"] } +rustix = { version = "1.1.2", features = ["termios", "event"] } diff --git a/image/src/kitty.rs b/image/src/kitty.rs index 58c4ba9a9..237add6cf 100644 --- a/image/src/kitty.rs +++ b/image/src/kitty.rs @@ -2,10 +2,9 @@ use anyhow::{Context as _, Result}; use base64::{engine, Engine}; use image::{imageops::FilterType, DynamicImage}; -use nix::poll::{poll, PollFd, PollFlags, PollTimeout}; -use nix::sys::termios::{tcgetattr, tcsetattr, LocalFlags, SetArg}; -use nix::unistd::read; -use rustix::termios::tcgetwinsize; +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 _; @@ -21,9 +20,9 @@ impl KittyBackend { let old = tcgetattr(&stdin).context("Failed to recieve terminal attibutes")?; let mut new = old.clone(); - new.local_flags &= !LocalFlags::ICANON; - new.local_flags &= !LocalFlags::ECHO; - tcsetattr(&stdin, SetArg::TCSANOW, &new) + new.local_modes &= !LocalModes::ICANON; + new.local_modes &= !LocalModes::ECHO; + tcsetattr(&stdin, OptionalActions::Now, &new) .context("Failed to update terminal attributes")?; old }; @@ -40,14 +39,15 @@ impl KittyBackend { stdout().flush()?; let start_time = Instant::now(); - let mut stdin_pollfd = [PollFd::new(stdin.as_fd(), PollFlags::POLLIN)]; + 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 poll(&mut stdin_pollfd, PollTimeout::ZERO)? < 1 { + while poll(&mut stdin_pollfd, Some(&Timespec::default()))? < 1 { if start_time.elapsed().as_millis() > 50 { - tcsetattr(&stdin, SetArg::TCSANOW, &old_attributes) + tcsetattr(&stdin, OptionalActions::Now, &old_attributes) .context("Failed to update terminal attributes")?; return Ok(false); } @@ -58,7 +58,7 @@ impl KittyBackend { buf.push(byte[0]); } if buf.starts_with(&[0x1B, b'_', b'G']) && buf.ends_with(&[0x1B, b'\\']) { - tcsetattr(&stdin, SetArg::TCSANOW, &old_attributes) + tcsetattr(&stdin, OptionalActions::Now, &old_attributes) .context("Failed to update terminal attributes")?; return Ok(true); } diff --git a/image/src/lib.rs b/image/src/lib.rs index abf446ba3..cbeea8ecb 100644 --- a/image/src/lib.rs +++ b/image/src/lib.rs @@ -1,7 +1,6 @@ use anyhow::Result; use image::DynamicImage; - #[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)] pub enum ImageProtocol { Kitty, diff --git a/image/src/sixel.rs b/image/src/sixel.rs index caa3f5cf5..0d586f8a0 100644 --- a/image/src/sixel.rs +++ b/image/src/sixel.rs @@ -5,10 +5,9 @@ use image::{ DynamicImage, GenericImageView, ImageBuffer, Pixel, Rgb, }; -use nix::poll::{poll, PollFd, PollFlags, PollTimeout}; -use nix::sys::termios::{tcgetattr, tcsetattr, LocalFlags, SetArg}; -use nix::unistd::read; -use rustix::termios::tcgetwinsize; +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; @@ -24,9 +23,9 @@ impl SixelBackend { let old = tcgetattr(&stdin).context("Failed to recieve terminal attibutes")?; let mut new = old.clone(); - new.local_flags &= !LocalFlags::ICANON; - new.local_flags &= !LocalFlags::ECHO; - tcsetattr(&stdin, SetArg::TCSANOW, &new) + new.local_modes &= !LocalModes::ICANON; + new.local_modes &= !LocalModes::ECHO; + tcsetattr(&stdin, OptionalActions::Now, &new) .context("Failed to update terminal attributes")?; old }; @@ -36,13 +35,14 @@ impl SixelBackend { stdout().flush()?; let start_time = Instant::now(); - let mut stdin_pollfd = [PollFd::new(stdin.as_fd(), PollFlags::POLLIN)]; + 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 poll(&mut stdin_pollfd, PollTimeout::ZERO)? < 1 { + while poll(&mut stdin_pollfd, Some(&Timespec::default()))? < 1 { if start_time.elapsed().as_millis() > 50 { - tcsetattr(stdin, SetArg::TCSANOW, &old_attributes) + tcsetattr(stdin, OptionalActions::Now, &old_attributes) .context("Failed to update terminal attributes")?; return Ok(false); } @@ -53,7 +53,7 @@ impl SixelBackend { 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'] { - tcsetattr(stdin, SetArg::TCSANOW, &old_attributes) + tcsetattr(stdin, OptionalActions::Now, &old_attributes) .context("Failed to update terminal attributes")?; return Ok(true); }