forked from o2sh/onefetch
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkitty.rs
134 lines (122 loc) · 4.82 KB
/
kitty.rs
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
use image::{imageops::FilterType, DynamicImage, GenericImageView};
use libc::{
c_void, ioctl, poll, pollfd, read, tcgetattr, tcsetattr, termios, winsize, ECHO, ICANON,
POLLIN, STDIN_FILENO, STDOUT_FILENO, TCSANOW, TIOCGWINSZ,
};
use std::time::Instant;
pub struct KittyBackend {}
impl KittyBackend {
pub fn new() -> Self {
Self {}
}
pub fn supported() -> bool {
// 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 mut new_attributes = old_attributes;
new_attributes.c_lflag &= !ICANON;
new_attributes.c_lflag &= !ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &new_attributes);
old_attributes
};
// generate red rgba test image
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 the test image with the action set to query
println!(
"\x1B_Gi=1,f=32,s=32,v=32,a=q;{}\x1B\\",
base64::encode(&test_image)
);
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 {
// check for timeout while polling to avoid blocking the main thread
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 super::ImageBackend for KittyBackend {
fn add_image(&self, lines: Vec<String>, image: &DynamicImage) -> 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);
// resize image to fit the text height with the Lanczos3 algorithm
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());
// convert the image to rgba samples
let rgba_image = image.to_rgba();
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); // image data is base64 encoded
let mut image_data = Vec::<u8>::new();
for chunk in encoded_image.as_bytes().chunks(4096) {
// send a 4096 byte chunk of base64 encoded rgba image data
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("\x1B\\".as_bytes());
}
image_data.extend("\x1B_Gm=0;\x1B\\".as_bytes()); // write empty last chunk
image_data.extend(format!("\x1B[{}A", image_rows as u32 - 1).as_bytes()); // move cursor to start of image
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()); // move cursor to end of image
String::from_utf8(image_data).unwrap()
}
}