A simple TUI Library written in Rust
at main 86 lines 2.7 kB view raw
1use std::env; 2use std::sync::atomic::{AtomicBool, Ordering}; 3 4/// Level of color support detected in the terminal. 5#[derive(Debug, Clone, Copy, PartialEq, Eq)] 6pub enum ColorLevel { 7 None, 8 Basic, 9 Color256, 10 TrueColor, 11} 12 13/// Get the terminal size as (columns, rows). 14/// Returns `None` if stdout is not a terminal. 15pub fn get_size() -> Option<(u16, u16)> { 16 unsafe { 17 let mut ws: libc::winsize = std::mem::zeroed(); 18 let ret = libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut ws); 19 if ret == 0 && ws.ws_col > 0 && ws.ws_row > 0 { 20 Some((ws.ws_col, ws.ws_row)) 21 } else { 22 None 23 } 24 } 25} 26 27/// Returns true if stdout is connected to a terminal (TTY). 28pub fn is_tty() -> bool { 29 unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 } 30} 31 32/// Detect the color support level of the terminal. 33/// Checks NO_COLOR, COLORTERM, and TERM environment variables. 34pub fn supports_color() -> ColorLevel { 35 if env::var("NO_COLOR").is_ok() { 36 return ColorLevel::None; 37 } 38 if !is_tty() { 39 return ColorLevel::None; 40 } 41 detect_color_level() 42} 43 44// --------------------------------------------------------------------------- 45// SIGWINCH resize detection 46// --------------------------------------------------------------------------- 47 48/// Set to `true` by the SIGWINCH signal handler when the terminal is resized. 49static RESIZE_PENDING: AtomicBool = AtomicBool::new(false); 50 51/// The SIGWINCH signal handler. Only performs an atomic store — async-signal-safe. 52extern "C" fn sigwinch_handler(_: libc::c_int) { 53 RESIZE_PENDING.store(true, Ordering::Relaxed); 54} 55 56/// Install a SIGWINCH handler that sets an internal flag on terminal resize. 57/// Idempotent — safe to call multiple times. 58pub fn install_resize_handler() { 59 unsafe { 60 libc::signal(libc::SIGWINCH, sigwinch_handler as libc::sighandler_t); 61 } 62} 63 64/// Returns `true` and clears the pending flag if a terminal resize occurred 65/// since the last call to this function (or since `install_resize_handler`). 66pub fn take_resize() -> bool { 67 RESIZE_PENDING.swap(false, Ordering::Relaxed) 68} 69 70fn detect_color_level() -> ColorLevel { 71 if let Ok(ct) = env::var("COLORTERM") 72 && (ct == "truecolor" || ct == "24bit") 73 { 74 return ColorLevel::TrueColor; 75 } 76 if let Ok(term) = env::var("TERM") { 77 if term == "xterm-256color" || term == "screen-256color" || term.contains("256") { 78 return ColorLevel::Color256; 79 } 80 if term == "dumb" { 81 return ColorLevel::None; 82 } 83 return ColorLevel::Basic; 84 } 85 ColorLevel::Basic 86}