Skip to content

Commit

Permalink
Make maximum timeout configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
bash committed Jan 17, 2024
1 parent 16817bd commit 9642bd9
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 23 deletions.
36 changes: 30 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
//!
//! ## Example: Test If the Terminal Uses a Dark Background
//! ```no_run
//! use term_color::background_color;
//! use term_color::{background_color, QueryOptions};
//!
//! let bg = background_color(QueryOptions::default());
//! // Perceived lightness is a value between 0 (black) and 100 (white)
//! let is_light = background_color().map(|c| c.perceived_lightness() >= 50).unwrap_or_default();
//! let is_light = bg.map(|c| c.perceived_lightness() >= 50).unwrap_or_default();
//! ```
//!
//! ## Variable Timeout
Expand Down Expand Up @@ -79,14 +80,37 @@ pub enum Error {
UnsupportedTerminal,
}

/// Options to be used with [`foreground_color`] and [`background_color`].
#[derive(Debug)]
#[non_exhaustive]
pub struct QueryOptions {
/// The maximum time spent waiting for a response from the terminal \
/// even when we *know* that the terminal supports querying for colors. Defaults to 1 s.
///
/// Note that this timeout might not always apply as we use a variable timeout
/// for the color query.
///
/// Consider leaving this on a high value as there might be a lot of latency \
/// between you and the terminal (e.g. when you're connected via SSH).
pub max_timeout: Duration,
}

impl Default for QueryOptions {
fn default() -> Self {
Self {
max_timeout: Duration::from_secs(1),
}
}
}

/// Queries the terminal for it's foreground color.
pub fn foreground_color() -> Result<Color> {
imp::foreground_color()
pub fn foreground_color(options: QueryOptions) -> Result<Color> {
imp::foreground_color(options)
}

/// Queries the terminal for it's background color.
pub fn background_color() -> Result<Color> {
imp::background_color()
pub fn background_color(options: QueryOptions) -> Result<Color> {
imp::background_color(options)
}

#[cfg(not(unix))]
Expand Down
4 changes: 3 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::error::Error;

use term_color::QueryOptions;

fn main() -> Result<(), Box<dyn Error>> {
let color = term_color::background_color()?;
let color = term_color::background_color(QueryOptions::default())?;
dbg!(color.perceived_lightness());
dbg!(color.perceived_lightness() <= 50);
dbg!(color);
Expand Down
27 changes: 13 additions & 14 deletions src/xterm.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
use crate::os::{poll_read, run_in_raw_mode, tty, Tty};
use crate::terminal::TerminalKind;
use crate::{Color, Error, Result};
use crate::{Color, Error, QueryOptions, Result};
use std::cmp::{max, min};
use std::io::{Read, Write as _};
use std::os::fd::AsRawFd;
use std::str::from_utf8;
use std::time::{Duration, Instant};

const MIN_TIMEOUT: Duration = Duration::from_millis(100);
const MAX_TIMEOUT: Duration = Duration::from_secs(1);

pub(crate) fn foreground_color() -> Result<Color> {
query_color("\x1b]10;?\x07", TerminalKind::from_env())
pub(crate) fn foreground_color(options: QueryOptions) -> Result<Color> {
query_color("\x1b]10;?\x07", options, TerminalKind::from_env())
}

pub(crate) fn background_color() -> Result<Color> {
query_color("\x1b]11;?\x07", TerminalKind::from_env())
pub(crate) fn background_color(options: QueryOptions) -> Result<Color> {
query_color("\x1b]11;?\x07", options, TerminalKind::from_env())
}

fn query_color(query: &str, terminal: TerminalKind) -> Result<Color> {
query_color_raw(query, terminal).and_then(parse_response)
fn query_color(query: &str, options: QueryOptions, terminal: TerminalKind) -> Result<Color> {
query_color_raw(query, options, terminal).and_then(parse_response)
}

fn parse_response(response: String) -> Result<Color> {
Expand All @@ -34,30 +33,30 @@ fn parse_response(response: String) -> Result<Color> {
.ok_or_else(|| Error::Parse(response))
}

fn query_color_raw(q: &str, terminal: TerminalKind) -> Result<String> {
fn query_color_raw(q: &str, options: QueryOptions, terminal: TerminalKind) -> Result<String> {
if let TerminalKind::Unsupported = terminal {
return Err(Error::UnsupportedTerminal);
}

let mut tty = tty()?;
run_in_raw_mode(tty.as_raw_fd(), move || match terminal {
TerminalKind::Unsupported => unreachable!(),
TerminalKind::Supported => Ok(query(&mut tty, q, MAX_TIMEOUT)?.0),
TerminalKind::Supported => Ok(query(&mut tty, q, options.max_timeout)?.0),
TerminalKind::Unknown => {
// We use a well-supported sequence such as CSI C to measure the latency.
// this is to avoid mixing up the case where the terminal is slow to respond
// (e.g. because we're connected via SSH and have a slow connection)
// with the case where the terminal does not support querying for colors.
let timeout = estimate_timeout(&mut tty)?;
let timeout = estimate_timeout(&mut tty, options.max_timeout)?;
Ok(query(&mut tty, q, timeout)?.0)
}
})
}

fn estimate_timeout(tty: &mut Tty) -> Result<Duration> {
let (_, latency) = query(tty, "\x1b[c", MAX_TIMEOUT)?;
fn estimate_timeout(tty: &mut Tty, max_timeout: Duration) -> Result<Duration> {
let (_, latency) = query(tty, "\x1b[c", max_timeout)?;
let timeout = latency * 2; // We want to be in the same ballpark as the latency of our test query. Factor 2 is mostly arbitrary.
Ok(min(max(timeout, MIN_TIMEOUT), MAX_TIMEOUT))
Ok(min(max(timeout, MIN_TIMEOUT), max_timeout))
}

fn query(tty: &mut Tty, query: &str, timeout: Duration) -> Result<(String, Duration)> {
Expand Down
4 changes: 2 additions & 2 deletions termtheme/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use term_color::background_color;
use term_color::{background_color, QueryOptions};

fn main() {
let theme = match background_color() {
let theme = match background_color(QueryOptions::default()) {
Ok(color) if color.perceived_lightness() <= 50 => "dark",
Ok(_) => "light",
Err(e) => {
Expand Down

0 comments on commit 9642bd9

Please sign in to comment.