// Supress warnings for `TIOCGWINSZ.into()` since freebsd requires it. #![allow(clippy::identity_conversion)] use libc::{ioctl, winsize, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ}; use std::mem::zeroed; /// Runs the ioctl command. Returns (0, 0) if all of the streams are not to a terminal, or /// there is an error. (0, 0) is an invalid size to have anyway, which is why /// it can be used as a nil value. unsafe fn get_dimensions_any() -> winsize { let mut window: winsize = zeroed(); let mut result = ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut window); if result == -1 { window = zeroed(); result = ioctl(STDIN_FILENO, TIOCGWINSZ.into(), &mut window); if result == -1 { window = zeroed(); result = ioctl(STDERR_FILENO, TIOCGWINSZ.into(), &mut window); if result == -1 { return zeroed(); } } } window } /// Runs the ioctl command. Returns (0, 0) if the output is not to a terminal, or /// there is an error. (0, 0) is an invalid size to have anyway, which is why /// it can be used as a nil value. unsafe fn get_dimensions_out() -> winsize { let mut window: winsize = zeroed(); let result = ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut window); if result != -1 { return window; } zeroed() } /// Runs the ioctl command. Returns (0, 0) if the input is not to a terminal, or /// there is an error. (0, 0) is an invalid size to have anyway, which is why /// it can be used as a nil value. unsafe fn get_dimensions_in() -> winsize { let mut window: winsize = zeroed(); let result = ioctl(STDIN_FILENO, TIOCGWINSZ.into(), &mut window); if result != -1 { return window; } zeroed() } /// Runs the ioctl command. Returns (0, 0) if the error is not to a terminal, or /// there is an error. (0, 0) is an invalid size to have anyway, which is why /// it can be used as a nil value. unsafe fn get_dimensions_err() -> winsize { let mut window: winsize = zeroed(); let result = ioctl(STDERR_FILENO, TIOCGWINSZ.into(), &mut window); if result != -1 { return window; } zeroed() } /// Query the current processes's output (`stdout`), input (`stdin`), and error (`stderr`) in /// that order, in the attempt to determine terminal width. If one of those streams is actually /// a tty, this function returns its width and height as a number of characters. /// /// # Errors /// /// If *all* of the streams are not ttys or return any errors this function will return `None`. /// /// # Example /// /// To get the dimensions of your terminal window, simply use the following: /// /// ```no_run /// if let Some((w, h)) = termize::dimensions() { /// println!("Width: {}\nHeight: {}", w, h); /// } else { /// println!("Unable to get term size :("); /// } /// ``` pub fn dimensions() -> Option<(usize, usize)> { let w = unsafe { get_dimensions_any() }; if w.ws_col == 0 || w.ws_row == 0 { None } else { Some((w.ws_col as usize, w.ws_row as usize)) } } /// Query the current processes's output (`stdout`) *only*, in the attempt to determine /// terminal width. If that stream is actually a tty, this function returns its width /// and height as a number of characters. /// /// # Errors /// /// If the stream is not a tty or return any errors this function will return `None`. /// /// # Example /// /// To get the dimensions of your terminal window, simply use the following: /// /// ```no_run /// if let Some((w, h)) = termize::dimensions_stdout() { /// println!("Width: {}\nHeight: {}", w, h); /// } else { /// println!("Unable to get term size :("); /// } /// ``` pub fn dimensions_stdout() -> Option<(usize, usize)> { let w = unsafe { get_dimensions_out() }; if w.ws_col == 0 || w.ws_row == 0 { None } else { Some((w.ws_col as usize, w.ws_row as usize)) } } /// Query the current processes's input (`stdin`) *only*, in the attempt to determine /// terminal width. If that stream is actually a tty, this function returns its width /// and height as a number of characters. /// /// # Errors /// /// If the stream is not a tty or return any errors this function will return `None`. /// /// # Example /// /// To get the dimensions of your terminal window, simply use the following: /// /// ```no_run /// if let Some((w, h)) = termize::dimensions_stdin() { /// println!("Width: {}\nHeight: {}", w, h); /// } else { /// println!("Unable to get term size :("); /// } /// ``` pub fn dimensions_stdin() -> Option<(usize, usize)> { let w = unsafe { get_dimensions_in() }; if w.ws_col == 0 || w.ws_row == 0 { None } else { Some((w.ws_col as usize, w.ws_row as usize)) } } /// Query the current processes's error output (`stderr`) *only*, in the attempt to dtermine /// terminal width. If that stream is actually a tty, this function returns its width /// and height as a number of characters. /// /// # Errors /// /// If the stream is not a tty or return any errors this function will return `None`. /// /// # Example /// /// To get the dimensions of your terminal window, simply use the following: /// /// ```no_run /// if let Some((w, h)) = termize::dimensions_stderr() { /// println!("Width: {}\nHeight: {}", w, h); /// } else { /// println!("Unable to get term size :("); /// } /// ``` pub fn dimensions_stderr() -> Option<(usize, usize)> { let w = unsafe { get_dimensions_err() }; if w.ws_col == 0 || w.ws_row == 0 { None } else { Some((w.ws_col as usize, w.ws_row as usize)) } } #[cfg(test)] mod tests { use super::dimensions; use std::process::{Command, Output, Stdio}; #[cfg(target_os = "macos")] fn stty_size() -> Output { Command::new("stty") .arg("-f") .arg("/dev/stderr") .arg("size") .stderr(Stdio::inherit()) .output() .unwrap() } #[cfg(not(target_os = "macos"))] fn stty_size() -> Output { Command::new("stty") .arg("-F") .arg("/dev/stderr") .arg("size") .stderr(Stdio::inherit()) .output() .expect("failed to run `stty_size()`") } #[test] fn test_shell() { let output = stty_size(); let stdout = String::from_utf8(output.stdout).expect("failed to turn into String"); let mut data = stdout.split_whitespace(); let rs = data .next() .unwrap_or("0") .parse::() .expect("failed to parse rows"); let cs = data .next() .unwrap_or("0") .parse::() .expect("failed to parse cols"); println!("stdout: {}", stdout); println!("rows: {}\ncols: {}", rs, cs); if let Some((w, h)) = dimensions() { assert_eq!(rs, h); assert_eq!(cs, w); } } }