use std::collections::HashMap;
use std::io::Write;
use std::time::{Duration, Instant};
const STDIN_FD: i32 = 0;
pub fn query_palette() -> HashMap<u8, (u8, u8, u8)> {
let mut map = HashMap::new();
for i in 0u8..16 {
match query_palette_rgb(i) {
Some(rgb) => {
map.insert(i, rgb);
}
None if i == 0 => break, None => {}
}
}
map
}
pub fn query_palette_rgb(index: u8) -> Option<(u8, u8, u8)> {
let mut out = std::io::stdout();
write!(out, "\x1b]4;{};?\x07", index).ok()?;
out.flush().ok()?;
let deadline = Instant::now() + Duration::from_millis(150);
let mut buf: Vec<u8> = Vec::with_capacity(64);
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() || !readable(STDIN_FD, remaining) {
break;
}
match read_byte(STDIN_FD) {
Some(b) => {
buf.push(b);
if b == 0x07 {
break;
}
if b == b'\\' && buf.len() >= 2 && buf[buf.len() - 2] == 0x1b {
break;
}
if buf.len() > 64 {
break;
}
}
None => break,
}
}
parse_rgb(&buf)
}
fn read_byte(fd: i32) -> Option<u8> {
let mut b = [0u8; 1];
let n = unsafe { libc::read(fd, b.as_mut_ptr() as *mut libc::c_void, 1) };
if n == 1 {
Some(b[0])
} else {
None
}
}
fn readable(fd: i32, timeout: Duration) -> bool {
let mut pfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
let ms = timeout.as_millis().min(i32::MAX as u128) as i32;
let rc = unsafe { libc::poll(&mut pfd, 1, ms) };
rc > 0 && (pfd.revents & libc::POLLIN) != 0
}
fn parse_rgb(buf: &[u8]) -> Option<(u8, u8, u8)> {
let s = String::from_utf8_lossy(buf);
let start = s.find("rgb:")? + 4;
let tail = &s[start..];
let end = tail.find(['\x1b', '\x07']).unwrap_or(tail.len());
let parts: Vec<&str> = tail[..end].split('/').collect();
if parts.len() < 3 {
return None;
}
Some((scale(parts[0])?, scale(parts[1])?, scale(parts[2])?))
}
fn scale(p: &str) -> Option<u8> {
let p = p.trim();
let v = u32::from_str_radix(p, 16).ok()?;
let scaled = match p.len() {
1 => v * 17, 2 => v, 3 => v >> 4, 4 => v / 257, _ => return None,
};
Some(scaled.min(255) as u8)
}