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