1 | // Supress warnings for `TIOCGWINSZ.into()` since freebsd requires it. |
2 | #![allow (clippy::identity_conversion)] |
3 | |
4 | use libc::{ioctl, winsize, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ}; |
5 | use std::mem::zeroed; |
6 | |
7 | /// Runs the ioctl command. Returns (0, 0) if all of the streams are not to a terminal, or |
8 | /// there is an error. (0, 0) is an invalid size to have anyway, which is why |
9 | /// it can be used as a nil value. |
10 | unsafe fn get_dimensions_any() -> winsize { |
11 | let mut window: winsize = zeroed(); |
12 | let mut result: i32 = ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut window); |
13 | |
14 | if result == -1 { |
15 | window = zeroed(); |
16 | result = ioctl(STDIN_FILENO, TIOCGWINSZ.into(), &mut window); |
17 | if result == -1 { |
18 | window = zeroed(); |
19 | result = ioctl(STDERR_FILENO, TIOCGWINSZ.into(), &mut window); |
20 | if result == -1 { |
21 | return zeroed(); |
22 | } |
23 | } |
24 | } |
25 | window |
26 | } |
27 | |
28 | /// Runs the ioctl command. Returns (0, 0) if the output is not to a terminal, or |
29 | /// there is an error. (0, 0) is an invalid size to have anyway, which is why |
30 | /// it can be used as a nil value. |
31 | unsafe fn get_dimensions_out() -> winsize { |
32 | let mut window: winsize = zeroed(); |
33 | let result: i32 = ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut window); |
34 | |
35 | if result != -1 { |
36 | return window; |
37 | } |
38 | zeroed() |
39 | } |
40 | |
41 | /// Runs the ioctl command. Returns (0, 0) if the input is not to a terminal, or |
42 | /// there is an error. (0, 0) is an invalid size to have anyway, which is why |
43 | /// it can be used as a nil value. |
44 | unsafe fn get_dimensions_in() -> winsize { |
45 | let mut window: winsize = zeroed(); |
46 | let result: i32 = ioctl(STDIN_FILENO, TIOCGWINSZ.into(), &mut window); |
47 | |
48 | if result != -1 { |
49 | return window; |
50 | } |
51 | zeroed() |
52 | } |
53 | |
54 | /// Runs the ioctl command. Returns (0, 0) if the error is not to a terminal, or |
55 | /// there is an error. (0, 0) is an invalid size to have anyway, which is why |
56 | /// it can be used as a nil value. |
57 | unsafe fn get_dimensions_err() -> winsize { |
58 | let mut window: winsize = zeroed(); |
59 | let result: i32 = ioctl(STDERR_FILENO, TIOCGWINSZ.into(), &mut window); |
60 | |
61 | if result != -1 { |
62 | return window; |
63 | } |
64 | zeroed() |
65 | } |
66 | |
67 | /// Query the current processes's output (`stdout`), input (`stdin`), and error (`stderr`) in |
68 | /// that order, in the attempt to determine terminal width. If one of those streams is actually |
69 | /// a tty, this function returns its width and height as a number of characters. |
70 | /// |
71 | /// # Errors |
72 | /// |
73 | /// If *all* of the streams are not ttys or return any errors this function will return `None`. |
74 | /// |
75 | /// # Example |
76 | /// |
77 | /// To get the dimensions of your terminal window, simply use the following: |
78 | /// |
79 | /// ```no_run |
80 | /// if let Some((w, h)) = termize::dimensions() { |
81 | /// println!("Width: {} \nHeight: {}" , w, h); |
82 | /// } else { |
83 | /// println!("Unable to get term size :(" ); |
84 | /// } |
85 | /// ``` |
86 | pub fn dimensions() -> Option<(usize, usize)> { |
87 | let w: winsize = unsafe { get_dimensions_any() }; |
88 | |
89 | if w.ws_col == 0 || w.ws_row == 0 { |
90 | None |
91 | } else { |
92 | Some((w.ws_col as usize, w.ws_row as usize)) |
93 | } |
94 | } |
95 | |
96 | /// Query the current processes's output (`stdout`) *only*, in the attempt to determine |
97 | /// terminal width. If that stream is actually a tty, this function returns its width |
98 | /// and height as a number of characters. |
99 | /// |
100 | /// # Errors |
101 | /// |
102 | /// If the stream is not a tty or return any errors this function will return `None`. |
103 | /// |
104 | /// # Example |
105 | /// |
106 | /// To get the dimensions of your terminal window, simply use the following: |
107 | /// |
108 | /// ```no_run |
109 | /// if let Some((w, h)) = termize::dimensions_stdout() { |
110 | /// println!("Width: {} \nHeight: {}" , w, h); |
111 | /// } else { |
112 | /// println!("Unable to get term size :(" ); |
113 | /// } |
114 | /// ``` |
115 | pub fn dimensions_stdout() -> Option<(usize, usize)> { |
116 | let w: winsize = unsafe { get_dimensions_out() }; |
117 | |
118 | if w.ws_col == 0 || w.ws_row == 0 { |
119 | None |
120 | } else { |
121 | Some((w.ws_col as usize, w.ws_row as usize)) |
122 | } |
123 | } |
124 | |
125 | /// Query the current processes's input (`stdin`) *only*, in the attempt to determine |
126 | /// terminal width. If that stream is actually a tty, this function returns its width |
127 | /// and height as a number of characters. |
128 | /// |
129 | /// # Errors |
130 | /// |
131 | /// If the stream is not a tty or return any errors this function will return `None`. |
132 | /// |
133 | /// # Example |
134 | /// |
135 | /// To get the dimensions of your terminal window, simply use the following: |
136 | /// |
137 | /// ```no_run |
138 | /// if let Some((w, h)) = termize::dimensions_stdin() { |
139 | /// println!("Width: {} \nHeight: {}" , w, h); |
140 | /// } else { |
141 | /// println!("Unable to get term size :(" ); |
142 | /// } |
143 | /// ``` |
144 | pub fn dimensions_stdin() -> Option<(usize, usize)> { |
145 | let w: winsize = unsafe { get_dimensions_in() }; |
146 | |
147 | if w.ws_col == 0 || w.ws_row == 0 { |
148 | None |
149 | } else { |
150 | Some((w.ws_col as usize, w.ws_row as usize)) |
151 | } |
152 | } |
153 | |
154 | /// Query the current processes's error output (`stderr`) *only*, in the attempt to dtermine |
155 | /// terminal width. If that stream is actually a tty, this function returns its width |
156 | /// and height as a number of characters. |
157 | /// |
158 | /// # Errors |
159 | /// |
160 | /// If the stream is not a tty or return any errors this function will return `None`. |
161 | /// |
162 | /// # Example |
163 | /// |
164 | /// To get the dimensions of your terminal window, simply use the following: |
165 | /// |
166 | /// ```no_run |
167 | /// if let Some((w, h)) = termize::dimensions_stderr() { |
168 | /// println!("Width: {} \nHeight: {}" , w, h); |
169 | /// } else { |
170 | /// println!("Unable to get term size :(" ); |
171 | /// } |
172 | /// ``` |
173 | pub fn dimensions_stderr() -> Option<(usize, usize)> { |
174 | let w: winsize = unsafe { get_dimensions_err() }; |
175 | |
176 | if w.ws_col == 0 || w.ws_row == 0 { |
177 | None |
178 | } else { |
179 | Some((w.ws_col as usize, w.ws_row as usize)) |
180 | } |
181 | } |
182 | |
183 | #[cfg (test)] |
184 | mod tests { |
185 | use super::dimensions; |
186 | use std::process::{Command, Output, Stdio}; |
187 | |
188 | #[cfg (target_os = "macos" )] |
189 | fn stty_size() -> Output { |
190 | Command::new("stty" ) |
191 | .arg("-f" ) |
192 | .arg("/dev/stderr" ) |
193 | .arg("size" ) |
194 | .stderr(Stdio::inherit()) |
195 | .output() |
196 | .unwrap() |
197 | } |
198 | |
199 | #[cfg (not(target_os = "macos" ))] |
200 | fn stty_size() -> Output { |
201 | Command::new("stty" ) |
202 | .arg("-F" ) |
203 | .arg("/dev/stderr" ) |
204 | .arg("size" ) |
205 | .stderr(Stdio::inherit()) |
206 | .output() |
207 | .expect("failed to run `stty_size()`" ) |
208 | } |
209 | |
210 | #[test ] |
211 | fn test_shell() { |
212 | let output = stty_size(); |
213 | let stdout = String::from_utf8(output.stdout).expect("failed to turn into String" ); |
214 | let mut data = stdout.split_whitespace(); |
215 | let rs = data |
216 | .next() |
217 | .unwrap_or("0" ) |
218 | .parse::<usize>() |
219 | .expect("failed to parse rows" ); |
220 | let cs = data |
221 | .next() |
222 | .unwrap_or("0" ) |
223 | .parse::<usize>() |
224 | .expect("failed to parse cols" ); |
225 | println!("stdout: {}" , stdout); |
226 | println!("rows: {}\ncols: {}" , rs, cs); |
227 | if let Some((w, h)) = dimensions() { |
228 | assert_eq!(rs, h); |
229 | assert_eq!(cs, w); |
230 | } |
231 | } |
232 | } |
233 | |