1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use anyhow::Result;
use image::{imageops::FilterType, DynamicImage};
use libc::{
c_void, ioctl, poll, pollfd, read, tcgetattr, tcsetattr, termios, winsize, ECHO, ICANON,
POLLIN, STDIN_FILENO, STDOUT_FILENO, TCSANOW, TIOCGWINSZ,
};
use std::io::{stdout, Write};
use std::time::Instant;
pub struct KittyBackend {}
impl KittyBackend {
pub fn new() -> Self {
Self {}
}
pub fn supported() -> bool {
let old_attributes = unsafe {
let mut old_attributes: termios = std::mem::zeroed();
tcgetattr(STDIN_FILENO, &mut old_attributes);
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 test_image = Vec::<u8>::with_capacity(32 * 32 * 4);
test_image.extend(
std::iter::repeat([255, 0, 0, 255].iter())
.take(32 * 32)
.flatten(),
);
print!(
"\x1B_Gi=1,f=32,s=32,v=32,a=q;{}\x1B\\",
base64::encode(&test_image)
);
stdout().flush().unwrap();
let start_time = Instant::now();
let mut stdin_pollfd = pollfd {
fd: STDIN_FILENO,
events: POLLIN,
revents: 0,
};
let allowed_bytes = [0x1B, b'_', b'G', b'\\'];
let mut buf = Vec::<u8>::new();
loop {
while unsafe { poll(&mut stdin_pollfd, 1, 0) < 1 } {
if start_time.elapsed().as_millis() > 50 {
unsafe {
tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes);
}
return 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);
}
if buf.starts_with(&[0x1B, b'_', b'G']) && buf.ends_with(&[0x1B, b'\\']) {
unsafe {
tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes);
}
return 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 {
let tty_size: winsize = std::mem::zeroed();
ioctl(STDOUT_FILENO, TIOCGWINSZ, &tty_size);
tty_size
};
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);
let image = image.resize(
u32::max_value(),
(lines.len() as f64 / height_ratio) as u32,
FilterType::Lanczos3,
);
let _image_columns = width_ratio * f64::from(image.width());
let image_rows = height_ratio * f64::from(image.height());
let rgba_image = image.to_rgba8();
let flat_samples = rgba_image.as_flat_samples();
let raw_image = flat_samples
.image_slice()
.expect("Conversion from image to rgba samples failed");
assert_eq!(
image.width() as usize * image.height() as usize * 4,
raw_image.len()
);
let encoded_image = base64::encode(raw_image); let mut image_data = Vec::<u8>::new();
for chunk in encoded_image.as_bytes().chunks(4096) {
image_data.extend(
format!(
"\x1B_Gf=32,s={},v={},m=1,a=T;",
image.width(),
image.height()
)
.as_bytes(),
);
image_data.extend(chunk);
image_data.extend(b"\x1B\\");
}
image_data.extend(b"\x1B_Gm=0;\x1B\\"); image_data.extend(format!("\x1B[{}A", image_rows as u32 - 1).as_bytes()); let mut i = 0;
for line in &lines {
image_data.extend(format!("\x1B[s{}\x1B[u\x1B[1B", line).as_bytes());
i += 1;
}
image_data
.extend(format!("\n\x1B[{}B", lines.len().max(image_rows as usize) - i).as_bytes()); Ok(String::from_utf8(image_data)?)
}
}