use std::io::Write;
use base64::{Engine, engine::general_purpose::STANDARD};
const SHIPIT_PNG: &[u8] = include_bytes!("../assets/shipit.png");
const IMG_COLS: u32 = 10;
const IMG_ROWS: u32 = 5;
pub fn render_shipit() -> bool {
try_kitty_render(SHIPIT_PNG)
.or_else(|| try_iterm2_render(SHIPIT_PNG))
.is_some()
}
fn is_kitty_capable() -> bool {
matches!(std::env::var("TERM").as_deref(), Ok("xterm-kitty"))
|| matches!(
std::env::var("TERM_PROGRAM").as_deref(),
Ok("kitty" | "ghostty" | "WezTerm")
)
}
fn is_iterm2_capable() -> bool {
matches!(
std::env::var("TERM_PROGRAM").as_deref(),
Ok("iTerm.app" | "WezTerm")
) || matches!(std::env::var("LC_TERMINAL").as_deref(), Ok("iTerm2"))
}
fn try_kitty_render(png_bytes: &[u8]) -> Option<()> {
if !is_kitty_capable() {
return None;
}
let encoded = STANDARD.encode(png_bytes);
let mut stdout = std::io::stdout().lock();
let chunk_size = 4096;
let chunks: Vec<&[u8]> = encoded.as_bytes().chunks(chunk_size).collect();
for (i, chunk) in chunks.iter().enumerate() {
let is_last = i == chunks.len() - 1;
let more = if is_last { 0 } else { 1 };
if i == 0 {
let _ = write!(
stdout,
"\x1b_Ga=T,f=100,c={IMG_COLS},r={IMG_ROWS},m={more};"
);
} else {
let _ = write!(stdout, "\x1b_Gm={more};");
}
let _ = stdout.write_all(chunk);
let _ = write!(stdout, "\x1b\\");
}
let _ = writeln!(stdout);
let _ = stdout.flush();
Some(())
}
fn try_iterm2_render(png_bytes: &[u8]) -> Option<()> {
if !is_iterm2_capable() {
return None;
}
let encoded = STANDARD.encode(png_bytes);
let name = STANDARD.encode("shipit.png");
let size = png_bytes.len();
let mut stdout = std::io::stdout().lock();
let _ = write!(
stdout,
"\x1b]1337;File=inline=1;width={IMG_COLS};height={IMG_ROWS};name={name};size={size}:{encoded}\x07"
);
let _ = writeln!(stdout);
let _ = stdout.flush();
Some(())
}