Skip to content

Commit de1778f

Browse files
committed
feat: safe kitty backend support checker
1 parent 88320f5 commit de1778f

File tree

3 files changed

+39
-44
lines changed

3 files changed

+39
-44
lines changed

image/src/kitty.rs

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
11
use crate::get_dimensions;
2-
use anyhow::Result;
2+
use anyhow::{Context as _, Result};
33
use base64::{engine, Engine};
44
use image::{imageops::FilterType, DynamicImage};
5-
use libc::{
6-
c_void, poll, pollfd, read, tcgetattr, tcsetattr, termios, ECHO, ICANON, POLLIN, STDIN_FILENO,
7-
TCSANOW,
8-
};
5+
6+
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
7+
use nix::sys::termios::{tcgetattr, tcsetattr, LocalFlags, SetArg};
8+
use nix::unistd::read;
9+
910
use std::io::{stdout, Write};
11+
use std::os::fd::AsFd as _;
1012
use std::time::Instant;
1113

1214
pub struct KittyBackend;
1315

1416
impl KittyBackend {
15-
pub fn supported() -> bool {
17+
pub fn supported() -> Result<bool> {
18+
let stdin = std::io::stdin();
1619
// save terminal attributes and disable canonical input processing mode
17-
let old_attributes = unsafe {
18-
let mut old_attributes: termios = std::mem::zeroed();
19-
tcgetattr(STDIN_FILENO, &mut old_attributes);
20+
let old_attributes = {
21+
let old = tcgetattr(&stdin).context("Failed to recieve terminal attibutes")?;
2022

21-
let mut new_attributes = old_attributes;
22-
new_attributes.c_lflag &= !ICANON;
23-
new_attributes.c_lflag &= !ECHO;
24-
tcsetattr(STDIN_FILENO, TCSANOW, &new_attributes);
25-
old_attributes
23+
let mut new = old.clone();
24+
new.local_flags &= !LocalFlags::ICANON;
25+
new.local_flags &= !LocalFlags::ECHO;
26+
tcsetattr(&stdin, SetArg::TCSANOW, &new)
27+
.context("Failed to update terminal attributes")?;
28+
old
2629
};
2730

2831
// generate red rgba test image
@@ -34,38 +37,30 @@ impl KittyBackend {
3437
"\x1B_Gi=1,f=32,s=32,v=32,a=q;{}\x1B\\",
3538
engine::general_purpose::STANDARD.encode(&test_image)
3639
);
37-
stdout().flush().unwrap();
40+
stdout().flush()?;
3841

3942
let start_time = Instant::now();
40-
let mut stdin_pollfd = pollfd {
41-
fd: STDIN_FILENO,
42-
events: POLLIN,
43-
revents: 0,
44-
};
43+
let mut stdin_pollfd = [PollFd::new(stdin.as_fd(), PollFlags::POLLIN)];
4544
let allowed_bytes = [0x1B, b'_', b'G', b'\\'];
4645
let mut buf = Vec::<u8>::new();
4746
loop {
4847
// check for timeout while polling to avoid blocking the main thread
49-
while unsafe { poll(&mut stdin_pollfd, 1, 0) < 1 } {
48+
while poll(&mut stdin_pollfd, PollTimeout::ZERO)? < 1 {
5049
if start_time.elapsed().as_millis() > 50 {
51-
unsafe {
52-
tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes);
53-
}
54-
return false;
50+
tcsetattr(&stdin, SetArg::TCSANOW, &old_attributes)
51+
.context("Failed to update terminal attributes")?;
52+
return Ok(false);
5553
}
5654
}
57-
let mut byte = 0;
58-
unsafe {
59-
read(STDIN_FILENO, &mut byte as *mut _ as *mut c_void, 1);
60-
}
61-
if allowed_bytes.contains(&byte) {
62-
buf.push(byte);
55+
let mut byte = [0];
56+
read(&stdin, &mut byte)?;
57+
if allowed_bytes.contains(&byte[0]) {
58+
buf.push(byte[0]);
6359
}
6460
if buf.starts_with(&[0x1B, b'_', b'G']) && buf.ends_with(&[0x1B, b'\\']) {
65-
unsafe {
66-
tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes);
67-
}
68-
return true;
61+
tcsetattr(&stdin, SetArg::TCSANOW, &old_attributes)
62+
.context("Failed to update terminal attributes")?;
63+
return Ok(true);
6964
}
7065
}
7166
}

image/src/lib.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@ pub trait ImageBackend {
1919
fn add_image(&self, lines: Vec<String>, image: &DynamicImage, colors: usize) -> Result<String>;
2020
}
2121

22-
pub fn get_best_backend() -> Option<Box<dyn ImageBackend>> {
22+
pub fn get_best_backend() -> Result<Option<Box<dyn ImageBackend>>> {
2323
#[cfg(not(windows))]
2424
if sixel::SixelBackend::supported() {
25-
Some(Box::new(sixel::SixelBackend))
26-
} else if kitty::KittyBackend::supported() {
27-
Some(Box::new(kitty::KittyBackend))
25+
Ok(Some(Box::new(sixel::SixelBackend)))
26+
} else if kitty::KittyBackend::supported()? {
27+
Ok(Some(Box::new(kitty::KittyBackend)))
2828
} else if iterm::ITermBackend::supported() {
29-
Some(Box::new(iterm::ITermBackend))
29+
Ok(Some(Box::new(iterm::ITermBackend)))
3030
} else {
31-
None
31+
Ok(None)
3232
}
3333

3434
#[cfg(windows)]
35-
None
35+
Ok(None)
3636
}
3737

3838
#[allow(unused_variables)]

src/ui/printer/factory.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ impl PrinterFactory {
3434
.image
3535
.image_protocol
3636
.map_or_else(onefetch_image::get_best_backend, |s| {
37-
onefetch_image::get_image_backend(s)
38-
})
37+
Ok(onefetch_image::get_image_backend(s))
38+
})?
3939
} else {
4040
None
4141
};

0 commit comments

Comments
 (0)