Refactor tutil::screen::unix slightly
[tutil.git] / src / screen / unix.rs
blob6734454febba306cb168349ec067de4d422f4335
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 //! Unix implementation of `tutil::screen`, tested on Linux, FreeBSD and macOS.
7 use super::{Width, Height};
9 use std::os::raw::c_ushort;
10 use libc::{ioctl, isatty, STDOUT_FILENO, TIOCGWINSZ};
12 /// The struct required by the `TIOCGWINSZ` syscall; specified in the following
13 /// [man page](http://www.delorie.com/djgpp/doc/libc/libc_495.html).
14 #[derive(Debug)]
15 struct WinSize {
16     /// Rows, in characters.
17     ws_row: c_ushort,
18     /// Columns, in characters.
19     ws_col: c_ushort,
20     /// Horizontal size, in pixels.
21     ws_xpixel: c_ushort,
22     /// Vertical size, in pixels.
23     ws_ypixel: c_ushort,
26 /// Returns the terminal screen size (in columns and rows).
27 ///
28 /// Returns `None` if the screen size is `(0, 0)` or is not able to be
29 /// determined.
30 pub fn size() -> Option<(Width, Height)> {
31     let is_tty = unsafe { isatty(STDOUT_FILENO) == 1 };
33     if !is_tty { return None; }
35     let mut winsize = WinSize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
37     let success: bool = unsafe {
38         ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut winsize) != 0
39     };
41     if success {
42         Some((Width(winsize.ws_col), Height(winsize.ws_row)))
43     } else {
44         None
45     }
48 /// Returns the terminal screen width (in columns).
49 ///
50 /// Returns `None` if the terminal width is detected as being <= 0 columns or is
51 /// not able to be determined at all.
52 pub fn width() -> Option<Width> {
53     let size = size();
55     if let Some((Width(width), Height(_))) = size {
56         Some(Width(width))
57     } else {
58         None
59     }
62 /// Returns the terminal screen height (in rows).
63 ///
64 /// Returns `None` if the terminal height is detected as being <= 0 rows or is
65 /// not able to be determined at all.
66 pub fn height() -> Option<Height> {
67     let size = size();
69     if let Some((Width(_), Height(height))) = size {
70         Some(Height(height))
71     } else {
72         None
73     }
76 #[cfg(test)]
77 mod test {
78     use super::*;
79     use super::super::{Width, Height};
81     use std::process::{Command, Stdio};
83     #[cfg(target_os = "linux")]
84     fn create_command() -> Command {
85         let mut cmd = Command::new("stty");
86         cmd.arg("--file");
87         cmd.arg("/dev/stderr");
88         cmd.arg("size");
89         cmd.stderr(Stdio::inherit());
90         cmd
91     }
93     #[cfg(any(target_os = "freebsd", target_os = "macos"))]
94     fn create_command() -> Command {
95         let mut cmd = Command::new("stty");
96         cmd.arg("-f");
97         cmd.arg("/dev/stderr");
98         cmd.arg("size");
99         cmd.stderr(Stdio::inherit());
100         cmd
101     }
103     #[test]
104     fn correct_size() {
105         let output = create_command().output().unwrap();
106         let stdout = String::from_utf8(output.stdout).unwrap();
107         assert!(output.status.success());
109         let cols = u16::from_str_radix(stdout.split_whitespace().last().unwrap(), 10).unwrap();
110         let rows = u16::from_str_radix(stdout.split_whitespace().next().unwrap(), 10).unwrap();
112         if let Some((Width(width), Height(height))) = size() {
113             assert_eq!(width, cols);
114             assert_eq!(height, rows);
115         }
116     }
118     #[test]
119     fn correct_width() {
120         let output = create_command().output().unwrap();
121         let stdout = String::from_utf8(output.stdout).unwrap();
122         assert!(output.status.success());
124         let cols = u16::from_str_radix(stdout.split_whitespace().last().unwrap(), 10).unwrap();
126         if let Some((Width(width), Height(_))) = size() {
127             assert_eq!(width, cols);
128         }
129     }
131     #[test]
132     fn correct_height() {
133         let output = create_command().output().unwrap();
134         let stdout = String::from_utf8(output.stdout).unwrap();
135         assert!(output.status.success());
137         let rows = u16::from_str_radix(stdout.split_whitespace().next().unwrap(), 10).unwrap();
139         if let Some((Width(_), Height(height))) = size() {
140             assert_eq!(height, rows);
141         }
142     }