From c8b32f73b278b9afd257b77aa12db6a58ec70a86 Mon Sep 17 00:00:00 2001 From: andrii <25188+unorsk@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:35:13 +0100 Subject: [PATCH] Smart terminal colors --- src/main.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 454fef1..fdc74ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -116,6 +116,8 @@ async fn inner_main() -> Result<(), WrappedErr> { optional -w,--white-color white: String /// Custom black color, specified in css format (e.g "000000" or "rgb(0, 0, 0)") optional -b,--black-color black: String + /// Use terminal foreground/background colors for the PDF + optional -t,--terminal-colors /// Print the version and exit optional --version /// PDF file to read @@ -137,6 +139,12 @@ async fn inner_main() -> Result<(), WrappedErr> { .canonicalize() .map_err(|e| WrappedErr(format!("Cannot canonicalize provided file: {e}").into()))?; + let (default_black, default_white) = if flags.terminal_colors { + query_terminal_colors() + } else { + (MUPDF_BLACK, MUPDF_WHITE) + }; + let black = flags .black_color .as_deref() @@ -151,7 +159,7 @@ async fn inner_main() -> Result<(), WrappedErr> { }) }) .transpose()? - .unwrap_or(MUPDF_BLACK); + .unwrap_or(default_black); let white = flags .white_color @@ -167,7 +175,7 @@ async fn inner_main() -> Result<(), WrappedErr> { }) }) .transpose()? - .unwrap_or(MUPDF_WHITE); + .unwrap_or(default_white); // need to keep it around throughout the lifetime of the program, but don't rly need to use it. // Just need to make sure it doesn't get dropped yet. @@ -565,6 +573,52 @@ fn parse_color_to_i32(cs: &str) -> Result Ok(i32::from_be_bytes([0, r, g, b])) } +fn query_terminal_colors() -> (i32, i32) { + let Ok(()) = enable_raw_mode() else { + return (MUPDF_BLACK, MUPDF_WHITE); + }; + + let fg = query_osc_color(10); + let bg = query_osc_color(11); + + let _ = disable_raw_mode(); + + (fg.unwrap_or(MUPDF_BLACK), bg.unwrap_or(MUPDF_WHITE)) +} + +fn query_osc_color(osc: u8) -> Option { + print!("\x1b]{osc};?\x1b\\"); + std::io::stdout().flush().unwrap(); + + let stdin = std::io::stdin(); + let mut handle = stdin.lock(); + let mut buf = Vec::new(); + let mut byte = [0u8; 1]; + loop { + handle.read_exact(&mut byte).ok()?; + if byte[0] == b'\\' || byte[0] == 0x07 { + break; + } + buf.push(byte[0]); + } + drop(handle); + + let input = String::from_utf8(buf).ok()?; + let rgb_str = input.split("rgb:").nth(1)?.trim_end_matches('\x1b'); + + let mut parts = rgb_str.split('/'); + let parse = |hex: &str| -> Option { + let val = u16::from_str_radix(hex, 16).ok()?; + Some(if hex.len() <= 2 { val as u8 } else { (val >> 8) as u8 }) + }; + + let r = parse(parts.next()?)?; + let g = parse(parts.next()?)?; + let b = parse(parts.next()?)?; + + Some(i32::from_be_bytes([0, r, g, b])) +} + fn get_font_size_through_stdio() -> Result<(u16, u16), WrappedErr> { // send the command code to get the terminal window size print!("\x1b[14t");