terminal_colorsaurus/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! Determines the background and foreground color of the terminal
4//! using the `OSC 10` and `OSC 11` terminal sequence.
5//!
6//! This crate helps answer the question *"Is this terminal dark or light?"*.
7//!
8//! ## Features
9//! * Background and foreground color detection.
10//! * Uses a fast and reliable heuristic to detect if the terminal supports color querying.
11//! * *Correct* perceived lightness calculation.
12//! * Works on Windows (starting with Windows Terminal v1.22).
13//! * Safely restores the terminal from raw mode even if the library errors or panicks.
14//! * Does not send any escape sequences if `TERM=dumb`.
15//! * Supports a timeout (for situations with high latency such as an SSH connection).
16//!
17//! ## Terminal Support
18//! `terminal-colorsaurus` works with most modern terminals and has been [tested extensively](`terminal_survey`).
19//! It's also really good at [detecting](`feature_detection`) when querying for the terminal's colors is not supported.
20//!
21//! ## Example 1: Test If the Terminal Uses a Dark Background
22//! ```no_run
23//! use terminal_colorsaurus::{theme_mode, ThemeMode, QueryOptions};
24//!
25//! let theme_mode = theme_mode(QueryOptions::default()).unwrap();
26//! dbg!(theme_mode == ThemeMode::Dark);
27//! ```
28//!
29//! ## Example 2: Get the Terminal's Foreground Color
30//! ```no_run
31//! use terminal_colorsaurus::{foreground_color, QueryOptions};
32//!
33//! let fg = foreground_color(QueryOptions::default()).unwrap();
34//! println!("rgb({}, {}, {})", fg.r, fg.g, fg.b);
35//! ```
36//!
37//! ## Optional Dependencies
38//! * [`rgb`] — Enable this feature to convert between [`Color`] and [`rgb::RGB16`] / [`rgb::RGB8`].
39//! * [`anstyle`] — Enable this feature to convert [`Color`] to [`anstyle::RgbColor`].
40
41use cfg_if::cfg_if;
42
43mod color;
44mod error;
45mod fmt;
46
47cfg_if! {
48 if #[cfg(all(any(unix, windows), not(terminal_colorsaurus_test_unsupported)))] {
49 mod io;
50 mod quirks;
51 mod xterm;
52 use xterm as imp;
53 } else {
54 mod unsupported;
55 use unsupported as imp;
56 }
57}
58
59cfg_if! {
60 if #[cfg(docsrs)] {
61 #[doc(cfg(docsrs))]
62 #[doc = include_str!("../doc/terminal-survey.md")]
63 pub mod terminal_survey {}
64
65 #[doc(cfg(docsrs))]
66 #[doc = include_str!("../doc/latency-rustdoc.md")]
67 pub mod latency {}
68
69 #[doc(cfg(docsrs))]
70 #[doc = include_str!("../doc/feature-detection.md")]
71 pub mod feature_detection {}
72
73 #[doc(cfg(docsrs))]
74 #[doc = include_str!("../doc/comparison.md")]
75 pub mod comparison {}
76 }
77}
78
79#[cfg(doctest)]
80#[doc = include_str!("../readme.md")]
81pub mod readme_doctests {}
82
83pub use color::*;
84
85/// The subset of the terminal's color palette needed for
86/// deriving the [`ThemeMode`], namely: the foreground and background color.
87/// Retrieved by calling [`color_palette`].
88#[derive(Debug, Clone, PartialEq, Eq, Hash)]
89#[non_exhaustive]
90pub struct ColorPalette {
91 /// The foreground color of the terminal.
92 pub foreground: Color,
93 /// The background color of the terminal.
94 pub background: Color,
95}
96
97/// The terminal's theme mode (i.e. dark or light).
98///
99/// You can retrieve it using [`theme_mode`].
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101#[allow(clippy::exhaustive_enums)]
102#[doc(alias = "color scheme")]
103pub enum ThemeMode {
104 /// The terminal uses a dark background with light text.
105 Dark,
106 /// The terminal uses a light background with dark text.
107 Light,
108}
109
110impl ColorPalette {
111 /// Determines if the terminal uses a dark or light background.
112 pub fn theme_mode(&self) -> ThemeMode {
113 let fg = self.foreground.perceived_lightness();
114 let bg = self.background.perceived_lightness();
115 if bg < fg {
116 ThemeMode::Dark
117 } else if bg > fg || bg > 0.5 {
118 ThemeMode::Light
119 } else {
120 ThemeMode::Dark
121 }
122 }
123}
124
125/// Result used by this library.
126pub type Result<T> = std::result::Result<T, Error>;
127pub use error::Error;
128
129/// Options to be used with [`foreground_color`] and [`background_color`].
130/// You should almost always use the unchanged [`QueryOptions::default`] value.
131#[derive(Debug, Clone, PartialEq, Eq)]
132#[non_exhaustive]
133pub struct QueryOptions {
134 /// The maximum time spent waiting for a response from the terminal. Defaults to 1 s.
135 ///
136 /// Consider leaving this on a high value as there might be a lot of latency \
137 /// between you and the terminal (e.g. when you're connected via SSH).
138 ///
139 /// Terminals that don't support querying for colors will
140 /// almost always be detected as such before this timeout elapses.
141 ///
142 /// See [Feature Detection](`feature_detection`) for details on how this works.
143 pub timeout: std::time::Duration,
144}
145
146impl Default for QueryOptions {
147 fn default() -> Self {
148 Self {
149 timeout: std::time::Duration::from_secs(1),
150 }
151 }
152}
153
154/// Detects if the terminal is dark or light.
155#[doc = include_str!("../doc/caveats.md")]
156#[doc(alias = "theme")]
157pub fn theme_mode(options: QueryOptions) -> Result<ThemeMode> {
158 color_palette(options).map(|p| p.theme_mode())
159}
160
161/// Queries the terminal for it's color palette (foreground and background color).
162#[doc = include_str!("../doc/caveats.md")]
163pub fn color_palette(options: QueryOptions) -> Result<ColorPalette> {
164 imp::color_palette(options)
165}
166
167/// Queries the terminal for it's foreground color. \
168/// If you also need the foreground color it is more efficient to use [`color_palette`] instead.
169#[doc = include_str!("../doc/caveats.md")]
170#[doc(alias = "fg")]
171pub fn foreground_color(options: QueryOptions) -> Result<Color> {
172 imp::foreground_color(options)
173}
174
175/// Queries the terminal for it's background color. \
176/// If you also need the foreground color it is more efficient to use [`color_palette`] instead.
177#[doc = include_str!("../doc/caveats.md")]
178#[doc(alias = "bg")]
179pub fn background_color(options: QueryOptions) -> Result<Color> {
180 imp::background_color(options)
181}
182
183#[cfg(test)]
184#[path = "theme_mode_tests.rs"]
185mod tests;