diff --git a/examples/detect_color.rs b/examples/detect_color.rs new file mode 100644 index 0000000..ecfdd5b --- /dev/null +++ b/examples/detect_color.rs @@ -0,0 +1,19 @@ +extern crate termion; + +use termion::color::{DetectColors, AnsiValue, Bg}; +use termion::raw::IntoRawMode; +use std::io::stdout; + +fn main() { + let count; + { + let mut term = stdout().into_raw_mode().unwrap(); + count = term.available_colors().unwrap(); + } + + println!("This terminal supports {} colors.", count); + for i in 0..count { + print!("{} {}", Bg(AnsiValue(i as u8)), Bg(AnsiValue(0))); + } + println!(); +} diff --git a/src/color.rs b/src/color.rs index c34517d..33a5ac2 100644 --- a/src/color.rs +++ b/src/color.rs @@ -13,6 +13,11 @@ //! ``` use std::fmt; +use raw::RawTerminal; +use std::io::{self, Write, Read}; +use std::time::{SystemTime, Duration}; +use async::async_stdin; +use std::env; /// A terminal color. pub trait Color { @@ -160,3 +165,76 @@ impl fmt::Display for Bg { self.0.write_bg(f) } } + +/// Types that allow detection of the colors they support. +pub trait DetectColors { + /// How many ANSI colors are supported (from 8 to 256)? + /// + /// Beware: the information given isn't authoritative, it's infered through escape codes or the + /// value of `TERM`, more colors may be available. + fn available_colors(&mut self) -> io::Result; +} + +impl DetectColors for RawTerminal { + fn available_colors(&mut self) -> io::Result { + let mut stdin = async_stdin(); + + if detect_color(self, &mut stdin, 0)? { + // OSC 4 is supported, detect how many colors there are. + // Do a binary search of the last supported color. + let mut min = 8; + let mut max = 256; + let mut i; + while min + 1 < max { + i = (min + max) / 2; + if detect_color(self, &mut stdin, i)? { + min = i + } else { + max = i + } + } + Ok(max) + } else { + // OSC 4 is not supported, trust TERM contents. + Ok(match env::var_os("TERM") { + Some(val) => { + if val.to_str().unwrap_or("").contains("256color") { + 256 + } else { + 8 + } + } + None => 8, + }) + } + } +} + +/// The timeout of an escape code control sequence, in milliseconds. +const CONTROL_SEQUENCE_TIMEOUT: u64 = 100; + +/// Detect a color using OSC 4. +fn detect_color(stdout: &mut RawTerminal, + stdin: &mut Read, + color: u16) + -> io::Result { + // Is the color available? + // Use `ESC ] 4 ; color ; ? BEL`. + write!(stdout, "\x1B]4;{};?\x07", color)?; + stdout.flush()?; + + let mut buf: [u8; 1] = [0]; + let mut total_read = 0; + + let timeout = Duration::from_millis(CONTROL_SEQUENCE_TIMEOUT); + let now = SystemTime::now(); + let bell = 7u8; + + // Either consume all data up to bell or wait for a timeout. + while buf[0] != bell && now.elapsed().unwrap() < timeout { + total_read += stdin.read(&mut buf)?; + } + + // If there was a response, the color is supported. + Ok(total_read > 0) +}