Skip to content

Commit df5eeeb

Browse files
committed
feat: safe sixel backend support checker
1 parent de1778f commit df5eeeb

File tree

2 files changed

+30
-35
lines changed

2 files changed

+30
-35
lines changed

image/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub trait ImageBackend {
2121

2222
pub fn get_best_backend() -> Result<Option<Box<dyn ImageBackend>>> {
2323
#[cfg(not(windows))]
24-
if sixel::SixelBackend::supported() {
24+
if sixel::SixelBackend::supported()? {
2525
Ok(Some(Box::new(sixel::SixelBackend)))
2626
} else if kitty::KittyBackend::supported()? {
2727
Ok(Some(Box::new(kitty::KittyBackend)))

image/src/sixel.rs

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,61 @@
11
use crate::get_dimensions;
2-
use anyhow::{Context, Result};
2+
use anyhow::{Context as _, Result};
33
use color_quant::NeuQuant;
44
use image::{
55
imageops::{colorops, FilterType},
66
DynamicImage, GenericImageView, ImageBuffer, Pixel, Rgb,
77
};
8-
use libc::{
9-
c_void, poll, pollfd, read, tcgetattr, tcsetattr, termios, ECHO, ICANON, POLLIN, STDIN_FILENO,
10-
TCSANOW,
11-
};
8+
9+
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
10+
use nix::sys::termios::{tcgetattr, tcsetattr, LocalFlags, SetArg};
11+
use nix::unistd::read;
12+
1213
use std::io::{stdout, Write};
14+
use std::os::fd::AsFd;
1315
use std::time::Instant;
1416

1517
pub struct SixelBackend;
1618

1719
impl SixelBackend {
18-
pub fn supported() -> bool {
20+
pub fn supported() -> Result<bool> {
21+
let stdin = std::io::stdin();
1922
// save terminal attributes and disable canonical input processing mode
20-
let old_attributes = unsafe {
21-
let mut old_attributes: termios = std::mem::zeroed();
22-
tcgetattr(STDIN_FILENO, &mut old_attributes);
23+
let old_attributes = {
24+
let old = tcgetattr(&stdin).context("Failed to recieve terminal attibutes")?;
2325

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

3134
// ask for the primary device attribute string
3235
print!("\x1B[c");
33-
stdout().flush().unwrap();
36+
stdout().flush()?;
3437

3538
let start_time = Instant::now();
36-
let mut stdin_pollfd = pollfd {
37-
fd: STDIN_FILENO,
38-
events: POLLIN,
39-
revents: 0,
40-
};
39+
let mut stdin_pollfd = [PollFd::new(stdin.as_fd(), PollFlags::POLLIN)];
4140
let mut buf = Vec::<u8>::new();
4241
loop {
4342
// check for timeout while polling to avoid blocking the main thread
44-
while unsafe { poll(&mut stdin_pollfd, 1, 0) < 1 } {
43+
while poll(&mut stdin_pollfd, PollTimeout::ZERO)? < 1 {
4544
if start_time.elapsed().as_millis() > 50 {
46-
unsafe {
47-
tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes);
48-
}
49-
return false;
45+
tcsetattr(stdin, SetArg::TCSANOW, &old_attributes)
46+
.context("Failed to update terminal attributes")?;
47+
return Ok(false);
5048
}
5149
}
52-
let mut byte = 0;
53-
unsafe {
54-
read(STDIN_FILENO, &mut byte as *mut _ as *mut c_void, 1);
55-
}
56-
buf.push(byte);
50+
let mut byte = [0];
51+
read(&stdin, &mut byte)?;
52+
buf.push(byte[0]);
5753
if buf.starts_with(&[0x1B, b'[', b'?']) && buf.ends_with(b"c") {
5854
for attribute in buf[3..(buf.len() - 1)].split(|x| *x == b';') {
5955
if attribute == [b'4'] {
60-
unsafe {
61-
tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes);
62-
}
63-
return true;
56+
tcsetattr(stdin, SetArg::TCSANOW, &old_attributes)
57+
.context("Failed to update terminal attributes")?;
58+
return Ok(true);
6459
}
6560
}
6661
}

0 commit comments

Comments
 (0)