From 9642bd93de4b0b7726a4abc5363f96bd9d9ae1b8 Mon Sep 17 00:00:00 2001 From: Tau Date: Wed, 17 Jan 2024 18:42:51 +0100 Subject: [PATCH] Make maximum timeout configurable --- src/lib.rs | 36 ++++++++++++++++++++++++++++++------ src/main.rs | 4 +++- src/xterm.rs | 27 +++++++++++++-------------- termtheme/src/main.rs | 4 ++-- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2d0d559..fcde7cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 @@ -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 { - imp::foreground_color() +pub fn foreground_color(options: QueryOptions) -> Result { + imp::foreground_color(options) } /// Queries the terminal for it's background color. -pub fn background_color() -> Result { - imp::background_color() +pub fn background_color(options: QueryOptions) -> Result { + imp::background_color(options) } #[cfg(not(unix))] diff --git a/src/main.rs b/src/main.rs index 1e353ec..c85269a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ use std::error::Error; +use term_color::QueryOptions; + fn main() -> Result<(), Box> { - 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); diff --git a/src/xterm.rs b/src/xterm.rs index 7f931f7..6aa5cc7 100644 --- a/src/xterm.rs +++ b/src/xterm.rs @@ -1,6 +1,6 @@ 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; @@ -8,18 +8,17 @@ 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 { - query_color("\x1b]10;?\x07", TerminalKind::from_env()) +pub(crate) fn foreground_color(options: QueryOptions) -> Result { + query_color("\x1b]10;?\x07", options, TerminalKind::from_env()) } -pub(crate) fn background_color() -> Result { - query_color("\x1b]11;?\x07", TerminalKind::from_env()) +pub(crate) fn background_color(options: QueryOptions) -> Result { + query_color("\x1b]11;?\x07", options, TerminalKind::from_env()) } -fn query_color(query: &str, terminal: TerminalKind) -> Result { - query_color_raw(query, terminal).and_then(parse_response) +fn query_color(query: &str, options: QueryOptions, terminal: TerminalKind) -> Result { + query_color_raw(query, options, terminal).and_then(parse_response) } fn parse_response(response: String) -> Result { @@ -34,7 +33,7 @@ fn parse_response(response: String) -> Result { .ok_or_else(|| Error::Parse(response)) } -fn query_color_raw(q: &str, terminal: TerminalKind) -> Result { +fn query_color_raw(q: &str, options: QueryOptions, terminal: TerminalKind) -> Result { if let TerminalKind::Unsupported = terminal { return Err(Error::UnsupportedTerminal); } @@ -42,22 +41,22 @@ fn query_color_raw(q: &str, terminal: TerminalKind) -> Result { 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 { - let (_, latency) = query(tty, "\x1b[c", MAX_TIMEOUT)?; +fn estimate_timeout(tty: &mut Tty, max_timeout: Duration) -> Result { + 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)> { diff --git a/termtheme/src/main.rs b/termtheme/src/main.rs index d324fbd..f5940ef 100644 --- a/termtheme/src/main.rs +++ b/termtheme/src/main.rs @@ -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) => {