use super::{Height, Width}; use std::os::unix::io::RawFd; /// Returns the size of the terminal defaulting to STDOUT, if available. /// /// If STDOUT is not a tty, returns `None` pub fn terminal_size() -> Option<(Width, Height)> { terminal_size_using_fd(libc::STDOUT_FILENO) } /// Returns the size of the terminal using the given file descriptor, if available. /// /// If the given file descriptor is not a tty, returns `None` pub fn terminal_size_using_fd(fd: RawFd) -> Option<(Width, Height)> { use libc::ioctl; use libc::isatty; use libc::{winsize as WinSize, TIOCGWINSZ}; let is_tty: bool = unsafe { isatty(fd) == 1 }; if !is_tty { return None; } let mut winsize = WinSize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0, }; if unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut winsize) } == -1 { return None; } let rows = winsize.ws_row; let cols = winsize.ws_col; if rows > 0 && cols > 0 { Some((Width(cols), Height(rows))) } else { None } } #[test] /// Compare with the output of `stty size` fn compare_with_stty() { use std::process::Command; use std::process::Stdio; let (rows, cols) = if cfg!(target_os = "illumos") { // illumos stty(1) does not accept a device argument, instead using // stdin unconditionally: let output = Command::new("stty") .stdin(Stdio::inherit()) .output() .unwrap(); assert!(output.status.success()); // stdout includes the row and columns thus: "rows = 80; columns = 24;" let vals = String::from_utf8(output.stdout) .unwrap() .lines() .map(|line| { // Split each line on semicolons to get "k = v" strings: line.split(';') .map(str::trim) .map(str::to_string) .collect::>() }) .flatten() .filter_map(|term| { // split each "k = v" string and look for rows/columns: match term.splitn(2, " = ").collect::>().as_slice() { ["rows", n] | ["columns", n] => Some(n.parse().unwrap()), _ => None, } }) .collect::>(); (vals[0], vals[1]) } else { let output = if cfg!(target_os = "linux") { Command::new("stty") .arg("size") .arg("-F") .arg("/dev/stderr") .stderr(Stdio::inherit()) .output() .unwrap() } else { Command::new("stty") .arg("-f") .arg("/dev/stderr") .arg("size") .stderr(Stdio::inherit()) .output() .unwrap() }; assert!(output.status.success()); let stdout = String::from_utf8(output.stdout).unwrap(); // stdout is "rows cols" let mut data = stdout.split_whitespace(); println!("{}", stdout); let rows = u16::from_str_radix(data.next().unwrap(), 10).unwrap(); let cols = u16::from_str_radix(data.next().unwrap(), 10).unwrap(); (rows, cols) }; println!("{} {}", rows, cols); if let Some((Width(w), Height(h))) = terminal_size() { assert_eq!(rows, h); assert_eq!(cols, w); } else { panic!("terminal_size() return None"); } }