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