use crate::io::{read_until2, TermReader};
use crate::quirks::{terminal_quirks_from_env, TerminalQuirks};
use crate::{Color, ColorPalette, Error, QueryOptions, Result};
use std::io::{self, BufRead, BufReader, Write as _};
use std::time::Duration;
use terminal_trx::{terminal, RawModeGuard};
const QUERY_FG: &[u8] = b"\x1b]10;?";
const FG_RESPONSE_PREFIX: &[u8] = b"\x1b]10;";
const QUERY_BG: &[u8] = b"\x1b]11;?";
const BG_RESPONSE_PREFIX: &[u8] = b"\x1b]11;";
pub(crate) fn foreground_color(options: QueryOptions) -> Result<Color> {
let quirks = terminal_quirks_from_env();
let response = query(
&options,
quirks,
|w| write_query(w, quirks, QUERY_FG),
read_color_response,
)
.map_err(map_timed_out_err(options.timeout))?;
parse_response(response, FG_RESPONSE_PREFIX)
}
pub(crate) fn background_color(options: QueryOptions) -> Result<Color> {
let quirks = terminal_quirks_from_env();
let response = query(
&options,
quirks,
|w| write_query(w, quirks, QUERY_BG),
read_color_response,
)
.map_err(map_timed_out_err(options.timeout))?;
parse_response(response, BG_RESPONSE_PREFIX)
}
pub(crate) fn color_palette(options: QueryOptions) -> Result<ColorPalette> {
let quirks = terminal_quirks_from_env();
let (fg_response, bg_response) = query(
&options,
quirks,
|w| write_query(w, quirks, QUERY_FG).and_then(|_| write_query(w, quirks, QUERY_BG)),
|r| Ok((read_color_response(r)?, read_color_response(r)?)),
)
.map_err(map_timed_out_err(options.timeout))?;
let foreground = parse_response(fg_response, FG_RESPONSE_PREFIX)?;
let background = parse_response(bg_response, BG_RESPONSE_PREFIX)?;
Ok(ColorPalette {
foreground,
background,
})
}
fn write_query(w: &mut dyn io::Write, quirks: TerminalQuirks, query: &[u8]) -> io::Result<()> {
quirks.write_all(w, query)?;
quirks.write_string_terminator(w)?;
Ok(())
}
fn map_timed_out_err(timeout: Duration) -> impl Fn(Error) -> Error {
move |e| match e {
Error::Io(io) if io.kind() == io::ErrorKind::TimedOut => Error::Timeout(timeout),
e => e,
}
}
const ST: &[u8] = b"\x1b\\";
const DA1: &[u8] = b"\x1b[c";
const ESC: u8 = 0x1b;
const BEL: u8 = 0x07;
fn parse_response(response: Vec<u8>, prefix: &[u8]) -> Result<Color> {
response
.strip_prefix(prefix)
.and_then(|r| r.strip_suffix(ST).or(r.strip_suffix(&[BEL])))
.and_then(xparsecolor)
.ok_or(Error::Parse(response))
}
fn xparsecolor(input: &[u8]) -> Option<Color> {
let xterm_color::Color {
red: r,
green: g,
blue: b,
..
} = xterm_color::Color::parse(input).ok()?;
Some(Color { r, g, b })
}
type Reader<'a> = BufReader<TermReader<RawModeGuard<'a>>>;
fn query<T>(
options: &QueryOptions,
quirks: TerminalQuirks,
write_query: impl FnOnce(&mut dyn io::Write) -> io::Result<()>,
read_response: impl FnOnce(&mut Reader<'_>) -> Result<T>,
) -> Result<T> {
if quirks.is_known_unsupported() {
return Err(Error::unsupported());
}
let mut tty = terminal()?;
if !tty.has_connected_stdio_stream() {
return Err(Error::unsupported());
}
let mut tty = tty.lock();
let mut tty = tty.enable_raw_mode()?;
write_query(&mut tty)?;
quirks.write_all(&mut tty, DA1)?;
tty.flush()?;
let mut reader = BufReader::with_capacity(32, TermReader::new(tty, options.timeout));
let response = read_response(&mut reader)?;
_ = consume_da1_response(&mut reader, true);
Ok(response)
}
fn read_color_response(r: &mut Reader<'_>) -> Result<Vec<u8>> {
let mut buf = Vec::new();
r.read_until(ESC, &mut buf)?;
if !r.buffer().starts_with(b"]") {
_ = consume_da1_response(r, false);
return Err(Error::unsupported());
}
read_until2(r, BEL, ESC, &mut buf)?;
if buf.last() == Some(&ESC) {
r.read_until(b'\\', &mut buf)?;
}
Ok(buf)
}
fn consume_da1_response(r: &mut impl BufRead, consume_esc: bool) -> io::Result<()> {
let mut buf = Vec::new();
if consume_esc {
r.read_until(ESC, &mut buf)?;
}
r.read_until(b'[', &mut buf)?;
r.read_until(b'c', &mut buf)?;
Ok(())
}