Add an initial implementation of tutil::screen
[tutil.git] / src / screen / unix.rs
blob6d30294589885f3898e5a027618a2ac7de9741d3
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`, should work on Linux, FreeBSD and OS X.
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.
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 (width, height) = unsafe {
36         let mut winsize = WinSize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
37         ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut winsize);
39         (winsize.ws_col, winsize.ws_row)
40     };
42     Some((Width(width), Height(height)))
45 /// Returns the terminal screen width.
46 ///
47 /// Returns `None` if the terminal width is detected as being <= 0 columns or is
48 /// not able to be determined at all.
49 pub fn width() -> Option<Width> {
50     let size = size();
52     if let Some((Width(width), Height(_))) = size {
53         Some(Width(width))
54     } else {
55         None
56     }
59 /// Returns the terminal height.
60 ///
61 /// Returns `None` if the terminal height is detected as being <= 0 rows or is
62 /// not able to be determined at all.
63 pub fn height() -> Option<Height> {
64     let size = size();
66     if let Some((Width(_), Height(height))) = size {
67         Some(Height(height))
68     } else {
69         None
70     }
73 #[cfg(test)]
74 mod test {
75     use super::*;
76     use super::super::{Width, Height};
78     use std::process::{Command, Stdio};
80     #[cfg(target_os = "linux")]
81     fn create_command() -> Command {
82         let mut cmd = Command::new("stty");
83         cmd.arg("--file");
84         cmd.arg("/dev/stderr");
85         cmd.arg("size");
86         cmd.stderr(Stdio::inherit());
87         cmd
88     }
90     #[cfg(any(target_os = "freebsd", target_os = "macos"))]
91     fn create_command() -> Command {
92         let mut cmd = Command::new("stty");
93         cmd.arg("-f");
94         cmd.arg("/dev/stderr");
95         cmd.arg("size");
96         cmd.stderr(Stdio::inherit());
97         cmd
98     }
100     #[test]
101     fn correct_width() {
102         let output = create_command().output().unwrap();
103         let stdout = String::from_utf8(output.stdout).unwrap();
104         assert!(output.status.success());
106         let cols = u16::from_str_radix(stdout.split_whitespace().last().unwrap(), 10).unwrap();
108         if let Some((Width(width), Height(_))) = size() {
109             assert_eq!(width, cols);
110         }
111     }
113     #[test]
114     fn correct_height() {
115        let output = create_command().output().unwrap();
116        let stdout = String::from_utf8(output.stdout).unwrap();
117         assert!(output.status.success());
119         let rows = u16::from_str_radix(stdout.split_whitespace().next().unwrap(), 10).unwrap();
121         if let Some((Width(_), Height(height))) = size() {
122             assert_eq!(height, rows);
123         }
124     }