1 | use super::{Height, Width}; |
2 | use rustix::fd::{BorrowedFd, AsRawFd}; |
3 | use std::os::unix::io::RawFd; |
4 | |
5 | /// Returns the size of the terminal. |
6 | /// |
7 | /// This function checks the stdout, stderr, and stdin streams (in that order). |
8 | /// The size of the first stream that is a TTY will be returned. If nothing |
9 | /// is a TTY, then `None` is returned. |
10 | pub fn terminal_size() -> Option<(Width, Height)> { |
11 | if let Some(size: (Width, Height)) = terminal_size_using_fd(std::io::stdout().as_raw_fd()) { |
12 | Some(size) |
13 | } else if let Some(size: (Width, Height)) = terminal_size_using_fd(std::io::stderr().as_raw_fd()) { |
14 | Some(size) |
15 | } else if let Some(size: (Width, Height)) = terminal_size_using_fd(std::io::stdin().as_raw_fd()) { |
16 | Some(size) |
17 | } else { |
18 | None |
19 | } |
20 | } |
21 | |
22 | /// Returns the size of the terminal using the given file descriptor, if available. |
23 | /// |
24 | /// If the given file descriptor is not a tty, returns `None` |
25 | pub fn terminal_size_using_fd(fd: RawFd) -> Option<(Width, Height)> { |
26 | use rustix::termios::{isatty, tcgetwinsize}; |
27 | |
28 | // TODO: Once I/O safety is stabilized, the enlosing function here should |
29 | // be unsafe due to taking a `RawFd`. We should then move the main |
30 | // logic here into a new function which takes a `BorrowedFd` and is safe. |
31 | let fd: BorrowedFd<'_> = unsafe { BorrowedFd::borrow_raw(fd) }; |
32 | |
33 | if !isatty(fd) { |
34 | return None; |
35 | } |
36 | |
37 | let winsize: winsize = tcgetwinsize(fd).ok()?; |
38 | |
39 | let rows: u16 = winsize.ws_row; |
40 | let cols: u16 = winsize.ws_col; |
41 | |
42 | if rows > 0 && cols > 0 { |
43 | Some((Width(cols), Height(rows))) |
44 | } else { |
45 | None |
46 | } |
47 | } |
48 | |
49 | #[test ] |
50 | /// Compare with the output of `stty size` |
51 | fn compare_with_stty() { |
52 | use std::process::Command; |
53 | use std::process::Stdio; |
54 | |
55 | let (rows, cols) = if cfg!(target_os = "illumos" ) { |
56 | // illumos stty(1) does not accept a device argument, instead using |
57 | // stdin unconditionally: |
58 | let output = Command::new("stty" ) |
59 | .stdin(Stdio::inherit()) |
60 | .output() |
61 | .unwrap(); |
62 | assert!(output.status.success()); |
63 | |
64 | // stdout includes the row and columns thus: "rows = 80; columns = 24;" |
65 | let vals = String::from_utf8(output.stdout) |
66 | .unwrap() |
67 | .lines() |
68 | .map(|line| { |
69 | // Split each line on semicolons to get "k = v" strings: |
70 | line.split(';' ) |
71 | .map(str::trim) |
72 | .map(str::to_string) |
73 | .collect::<Vec<_>>() |
74 | }) |
75 | .flatten() |
76 | .filter_map(|term| { |
77 | // split each "k = v" string and look for rows/columns: |
78 | match term.splitn(2, " = " ).collect::<Vec<_>>().as_slice() { |
79 | ["rows" , n] | ["columns" , n] => Some(n.parse().unwrap()), |
80 | _ => None, |
81 | } |
82 | }) |
83 | .collect::<Vec<_>>(); |
84 | (vals[0], vals[1]) |
85 | } else { |
86 | let output = if cfg!(target_os = "linux" ) { |
87 | Command::new("stty" ) |
88 | .arg("size" ) |
89 | .arg("-F" ) |
90 | .arg("/dev/stderr" ) |
91 | .stderr(Stdio::inherit()) |
92 | .output() |
93 | .unwrap() |
94 | } else { |
95 | Command::new("stty" ) |
96 | .arg("-f" ) |
97 | .arg("/dev/stderr" ) |
98 | .arg("size" ) |
99 | .stderr(Stdio::inherit()) |
100 | .output() |
101 | .unwrap() |
102 | }; |
103 | |
104 | assert!(output.status.success()); |
105 | let stdout = String::from_utf8(output.stdout).unwrap(); |
106 | // stdout is "rows cols" |
107 | let mut data = stdout.split_whitespace(); |
108 | println!(" {}" , stdout); |
109 | let rows = u16::from_str_radix(data.next().unwrap(), 10).unwrap(); |
110 | let cols = u16::from_str_radix(data.next().unwrap(), 10).unwrap(); |
111 | (rows, cols) |
112 | }; |
113 | println!(" {} {}" , rows, cols); |
114 | |
115 | if let Some((Width(w), Height(h))) = terminal_size() { |
116 | assert_eq!(rows, h); |
117 | assert_eq!(cols, w); |
118 | } else { |
119 | panic!("terminal_size() return None" ); |
120 | } |
121 | } |
122 | |