Browse Source

No unsafe (#1646)

pull/1651/head
Fedor Finenko 2 weeks ago committed by GitHub
parent
commit
0ae82b3a1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 94
      Cargo.lock
  2. 2
      image/Cargo.toml
  3. 16
      image/src/iterm.rs
  4. 81
      image/src/kitty.rs
  5. 37
      image/src/lib.rs
  6. 79
      image/src/sixel.rs
  7. 4
      src/ui/printer/factory.rs

94
Cargo.lock generated

@ -741,11 +741,11 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" @@ -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 = [ @@ -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 = [ @@ -2970,7 +2970,7 @@ dependencies = [
"clap",
"color_quant",
"image",
"libc",
"rustix",
]
[[package]]
@ -3563,9 +3563,9 @@ dependencies = [ @@ -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" @@ -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" @@ -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" @@ -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 = [ @@ -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 = [ @@ -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" @@ -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" @@ -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" @@ -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" @@ -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" @@ -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" @@ -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"

2
image/Cargo.toml

@ -15,4 +15,4 @@ image.workspace = true @@ -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"] }

16
image/src/iterm.rs

@ -1,29 +1,19 @@ @@ -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 { @@ -31,7 +21,7 @@ impl super::ImageBackend for ITermBackend {
image: &DynamicImage,
_colors: usize,
) -> Result<String> {
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);

81
image/src/kitty.rs

@ -1,32 +1,30 @@ @@ -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<bool> {
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 { @@ -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::<u8>::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 { @@ -88,7 +73,7 @@ impl super::ImageBackend for KittyBackend {
image: &DynamicImage,
_colors: usize,
) -> Result<String> {
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);

37
image/src/lib.rs

@ -19,47 +19,32 @@ pub trait ImageBackend { @@ -19,47 +19,32 @@ pub trait ImageBackend {
fn add_image(&self, lines: Vec<String>, image: &DynamicImage, colors: usize) -> Result<String>;
}
pub fn get_best_backend() -> Option<Box<dyn ImageBackend>> {
pub fn get_best_backend() -> Result<Option<Box<dyn ImageBackend>>> {
#[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<Box<dyn ImageBackend>> {
#[cfg(not(windows))]
let backend = Some(match image_protocol {
ImageProtocol::Kitty => Box::new(kitty::KittyBackend::new()) as Box<dyn ImageBackend>,
ImageProtocol::Iterm => Box::new(iterm::ITermBackend::new()) as Box<dyn ImageBackend>,
ImageProtocol::Sixel => Box::new(sixel::SixelBackend::new()) as Box<dyn ImageBackend>,
ImageProtocol::Kitty => Box::new(kitty::KittyBackend) as Box<dyn ImageBackend>,
ImageProtocol::Iterm => Box::new(iterm::ITermBackend) as Box<dyn ImageBackend>,
ImageProtocol::Sixel => Box::new(sixel::SixelBackend) as Box<dyn ImageBackend>,
});
#[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
}
}

79
image/src/sixel.rs

@ -1,70 +1,61 @@ @@ -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<bool> {
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::<u8>::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 { @@ -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<String>, image: &DynamicImage, colors: usize) -> Result<String> {
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;

4
src/ui/printer/factory.rs

@ -34,8 +34,8 @@ impl PrinterFactory { @@ -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
};

Loading…
Cancel
Save