Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 68 additions & 26 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion image/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 3 additions & 13 deletions image/src/iterm.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
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,
lines: Vec<String>,
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);

Expand Down
81 changes: 33 additions & 48 deletions image/src/kitty.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -38,57 +36,44 @@ 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,
lines: Vec<String>,
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);

Expand Down
Loading